Slide 1

Slide 1 text

App to App Designing Local APIs on Android Ty Smith Android Engineer at Twitter 1 @tsmith

Slide 2

Slide 2 text

2 @tsmith

Slide 3

Slide 3 text

3 @tsmith

Slide 4

Slide 4 text

4 @tsmith

Slide 5

Slide 5 text

Integration Requirements • Get Note(s) • List Notes • Create/Update Notes • Delete Notes • Account Sync • Get Preferences 5 @tsmith

Slide 6

Slide 6 text

Android Components Needed 1. Intents 2. Content Provider 3. Account Manager 4. Sync Adapter 5. Inter Process Communication 6. Permissions 6 @tsmith

Slide 7

Slide 7 text

Intents Overview • Simple message between two components • Bundle - key/value data • Limited payload size (1MB) • Inefficient for batch operations • Defined Action 7 @tsmith

Slide 8

Slide 8 text

Intents Standard Actions • ACTION_SEND • ACTION_SEND_MULTIPLE • ACTION_VIEW • ACTION_EDIT 8 @tsmith

Slide 9

Slide 9 text

Editing Intent Sender public void editImage(String mimeType, Uri imageUri) { Intent intent = new Intent(Intent.ACTION_EDIT); intent.setType(mimeType); intent.setData(imageUri); startActivityForResult(intent, RESULT_CODE); } 9 @tsmith

Slide 10

Slide 10 text

Editing Intent Receiver 10 @tsmith

Slide 11

Slide 11 text

Editing Intent Receiver @Override protected void onNewIntent(Intent intent) { switch(intent.getAction()) { case Intent.ACTION_EDIT: if (intent.getType().startsWith("image/")) { editImage(intent.getData()); setResult(RESULT_OK, intent); finish(); } } } 11 @tsmith

Slide 12

Slide 12 text

Editing Intent Sender @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode = RESULT_CODE && RESULT_OK == resultCode) { Uri imageUri = data.getData(); //Update UI with new image } } 12 @tsmith

Slide 13

Slide 13 text

Intents Edit Caveats • Use copy of file • Don’t rely on setResult() • Use a ContentObserver • Check for file modified 13 @tsmith

Slide 14

Slide 14 text

Intents Custom Actions com.example.action.note.* list - view - new - create edit - update - delete 14 @tsmith

Slide 15

Slide 15 text

Intents Custom Actions public void newNoteWithContent(Uri image) { Intent intent = new Intent(); intent.setAction(NEW_NOTE); intent.putExtra(Intent.EXTRA_TITLE, "LET ME EXPLAIN YOU INTENTS"); intent.putExtra(Intent.EXTRA_TEXT, "¯\_()_/¯"); intent.setData(image); startActivityForResult(intent); } 15 @tsmith

Slide 16

Slide 16 text

Content Provider A well defined database access layer • SQLite • Interact with large volumes of data • Authority 16 @tsmith

Slide 17

Slide 17 text

Content Provider URI content:// com.example / users / 1 Scheme Authority Data Id 17 @tsmith

Slide 18

Slide 18 text

Content Provider Query public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); switch (URIMatcher.match(uri)) { USERS_LIST: queryBuilder.setTables(MyDBHandler.TABLE_USERS); } Cursor cursor = queryBuilder.query(myDB.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } 18 @tsmith

Slide 19

Slide 19 text

Content Resolver Query Uri uri = Uri.parse("content://com.example/users"); String[] projection = new String[]{"username", “email”}; String selection = “name LIKE ?”; String[] args = new String[]{"Ty"}; String sort = “email ASC”; getContentResolver().query(uri, projection, selection, args, sort); 19 @tsmith

Slide 20

Slide 20 text

Content Provider Serving Files @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { File path = new File(getContext().getCacheDir(), uri.getEncodedPath()); int imode = 0; if (mode.contains("w")) { imode |= ParcelFileDescriptor.MODE_WRITE_ONLY; if (!path.exists()) { try { path.createNewFile(); //TODO: Handle IOException } } if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY; if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND; return ParcelFileDescriptor.open(path, imode); } 20 @tsmith

Slide 21

Slide 21 text

Content Resolver Getting Files ContentResolver resolver = getContentResolver(); URI imageUri = Uri.parse("content://com.example/images/1"); String mode = "rw+" ParcelFileDescriptor pfd = resolver.openFileDescriptor(imageUri, mode); FileDescriptor fileDescriptor = pfd.getFileDescriptor(); InputStream fileStream = new FileInputStream(fileDescriptor); //Magic here! 21 @tsmith

Slide 22

Slide 22 text

Account Manager • Accessible Account • Authentication Token • Consistency 22 @tsmith

Slide 23

Slide 23 text

Providing your own Accounts • AbstractAccountAuthenticator • AccountAuthenticatorActivity 23 @tsmith

Slide 24

Slide 24 text

24 @tsmith

Slide 25

Slide 25 text

AbstractAccountAuthenticator addAccount @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType); intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType); intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } 25 @tsmith

Slide 26

Slide 26 text

AbstractAccountAuthenticator getAuthToken @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { final AccountManager am = AccountManager.get(mContext); String authToken = am.peekAuthToken(account, authTokenType); if (TextUtils.isEmpty(authToken)) { final String password = am.getPassword(account); if (password != null) { authToken = serverAuthenticate.userSignIn(account.name, password, authTokenType); } } ... 26 @tsmith

Slide 27

Slide 27 text

AbstractAccountAuthenticator getAuthToken ... if (!TextUtils.isEmpty(authToken)) { final Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); return result; } ... 27 @tsmith

Slide 28

Slide 28 text

AbstractAccountAuthenticator getAuthToken ... final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type); intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } 28 @tsmith

Slide 29

Slide 29 text

AccountAuthenticatorActivity Success private void finishLogin(String accountName, String accountType, String password, String authToken, String authTokenType) { Account account = new Account(accountName, accountType); if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) { accountManager.addAccountExplicitly(account, password, null); } accountManager.setPassword(account, password); accountManager.setAuthToken(account, authTokenType, authToken); Intent intent = new Intent(); intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, userName); intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); intent.putExtra(AccountManager.KEY_AUTHTOKEN, authToken); setAccountAuthenticatorResult(intent.getExtras()); setResult(RESULT_OK, intent); finish(); } 29 @tsmith

Slide 30

Slide 30 text

Sync Adapter • Sync App and Web Service • User visible syncing • Network/battery optimized • Provided to third party apps • Schedule and GCM 30 @tsmith

Slide 31

Slide 31 text

Sync Adapter public class ImagesSyncAdapter extends AbstractThreadedSyncAdapter { private final AccountManager accountManager; public TvShowsSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); accountManager = AccountManager.get(context); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { String authToken = accountManager.blockingGetAuthToken(account, AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, true); List images = getImagesFromServer(authToken); updateImagesOnDisk(provider, images); } } 31 @tsmith

Slide 32

Slide 32 text

Sync Adapter public class ImagesSyncService extends Service { private static final Object syncAdapterLock = new Object(); private static ImagesSyncAdapter syncAdapter = null; @Override public void onCreate() { synchronized (syncAdapterLock) { if (syncAdapter == null) syncAdapter = new ImagesSyncAdapter(getApplicationContext(), true); } } @Override public IBinder onBind(Intent intent) { return syncAdapter.getSyncAdapterBinder(); } } 32 @tsmith

Slide 33

Slide 33 text

Sync Adapter res/xml/sync_adapter.xml 33 @tsmith

Slide 34

Slide 34 text

Sync Adapter AndroidManifest.xml 34 @tsmith

Slide 35

Slide 35 text

Sync Adapter Sync Every Hour int interval = 3600; ContentResolver.addPeriodicSync(account, AppContract.AUTHORITY, new Bundle(), interval); 35 @tsmith

Slide 36

Slide 36 text

Sync Adapter Jitter int jitteredInterval = 3600 + new Random().nextInt(300); ContentResolver.addPeriodicSync(account, AppContract.AUTHORITY, new Bundle(), jitteredInterval); 36 @tsmith

Slide 37

Slide 37 text

Binding Services for IPC • Android Interface Definition Language (AIDL) • Communicate between process or app • Directly call defined RPC methods • Requires consumer to contain interface 37 @tsmith

Slide 38

Slide 38 text

Binding Services Create AIDL interface RemoteMessageService { void passMessage(String "message"); } 38 @tsmith

Slide 39

Slide 39 text

Binding Services Implement AIDL public class MessageBinder implements RemoteMessageService.Stub() { public void passMessage(String message) { Log.d(TAG, message); } } 39 @tsmith

Slide 40

Slide 40 text

Binding Services Expose Interface public class RemoteService extends Service { @Override public IBinder onBind(Intent intent) { return new MessageBinder; } } 40 @tsmith

Slide 41

Slide 41 text

Binding Services Connecting private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { service = IRemoteService.Stub.asInterface(service); } public void onServiceDisconnected(ComponentName className) { service = null; } } public void onResume() { bindService(new Intent(this, RemoteMessageService.class), connection, Context.BIND_AUTO_CREATE); } public void onPause() { unbindService(connection); service = null; } 41 @tsmith

Slide 42

Slide 42 text

Permissions • Inform the User • Mitigate Exploits 42 @tsmith

Slide 43

Slide 43 text

Permissions Levels • Normal • Dangerous • Signature • SignatureOrSystem 43 @tsmith

Slide 44

Slide 44 text

Permissions Custom • Can be used to restrict access to various services and components • Required to interact with app that declares it • Can be checked programatically or via Manifest. 44 @tsmith

Slide 45

Slide 45 text

Permissions Enforcing Programitcally int canProcess = getContext().checkCallingPermission("com.example.perm.READ"); if (canProcess != PERMISSION_GRANTED) { throw new SecurityException("Requires Custom Permission"); } • checkCallingOrSelfPermission() can leak permissions 45 @tsmith

Slide 46

Slide 46 text

Permissions Enforcing via XML 46 @tsmith

Slide 47

Slide 47 text

Debugging Tips • Debugging app integrations is hard • Logging is your friend • Test with mock integrations • Communicate your data contracts • Seek User Feedback • Analytics and Crash Reporting 47 @tsmith

Slide 48

Slide 48 text

Integration Requirements • Get Note(s) (Intent & Content Provider) • List Notes (Intent) • Create/Update Notes (Intent & Content Provider) • Delete Notes (Intent & Content Provider) • Account Sync (Account Manager & Sync Adapter) • Get Preferences (Content Provider and bound Services) 48 @tsmith

Slide 49

Slide 49 text

Resources Example App Dashclock for Evernote Android Developer Docs on AIDL Write your own Android Authenticator 49 @tsmith

Slide 50

Slide 50 text

Thanks! Ty Smith @tsmith 50 @tsmith