In its continuous effort to improve user experience, the Android platform has introduced strict limitations on background services starting in API level 26. Basically, unless your app is running in the foreground, the system will stop all of your app's background services within minutes.
As a result of these restrictions on background services, JobScheduler jobs have become the de facto solution for performing background tasks. For people familiar with services, JobScheduler is generally straightforward to use: except in a few cases, one of which we shall explore presently.
JobScheduler
Imagine you are building an Android TV app. Since channels are very important to TV Apps, your app should be able to perform at least five different background operations on channels: publish a channel, add programs to a channel, send logs about a channel to your remote server, update a channel's metadata, and delete a channel. Prior to Android 8.0 (Oreo) each of these five operations could be implemented within background services. Starting in API 26, however, you must be judicious in deciding which should be plain old background Services and which should be JobServices.
Service
JobService
In the case of a TV app, of the five operations mentioned above, only channel publication can be a plain old background service. For some context, channel publication involves three steps: first the user clicks on a button to start the process; second the app starts a background operation to create and submit the publication; and third, the user gets a UI to confirm subscription. So as you can see, publishing channels requires user interactions and therefore a visible Activity. Hence, ChannelPublisherService could be an IntentService that handles the background portion. The reason you should not use a JobService here is because JobService will introduce a delay in execution, whereas user interaction usually requires immediate response from your app.
IntentService
For the other four operations, however, you should use JobServices; that's because all of them may execute while your app is in the background. So respectively, you should have ChannelProgramsJobService, ChannelLoggerJobService, ChannelMetadataJobService, and ChannelDeletionJobService.
ChannelProgramsJobService
ChannelLoggerJobService
ChannelMetadataJobService
ChannelDeletionJobService
Since all the four JobServices above deal with Channel objects, it should be convenient to use the channelId as the jobId for each one of them. But because of the way JobServices are designed in the Android Framework, you can't. The following is the official description of jobId
Channel
channelId
jobId
Application-provided id for this job. Subsequent calls to cancel, or jobs created with the same jobId, will update the pre-existing job with the same id. This ID must be unique across all clients of the same uid (not just the same package). You will want to make sure this is a stable id across app updates, so probably not based on a resource ID.
What the description is telling you is that even though you are using 4 different Java objects (i.e. -JobServices), you still cannot use the same channelId as their jobIds. You don't get credit for class-level namespace.
This indeed is a real problem. You need a stable and scalable way to relate a channelId to its set of jobIds. The last thing you want is to have different channels overwriting each other's operations because of jobId collisions. Were jobId of type String instead of Integer, the solution would be easy: jobId= "ChannelPrograms" + channelId for ChannelProgramsJobService, jobId= "ChannelLogs" + channelId for ChannelLoggerJobService, etc. But since jobId is an Integer and not a String, you have to devise a clever system for generating reusable jobIds for your jobs. And for that, you can use something like the following JobIdManager.
jobId= "ChannelPrograms" + channelId
ChannelProgramsJobService, jobId= "ChannelLogs" + channelId
ChannelLoggerJobService,
JobIdManager
JobIdManager is a class that you tweak according to your app's needs. For this present TV app, the basic idea is to use a single channelId over all jobs dealing with Channels. To expedite clarification: let's first look at the code for this sample JobIdManager class, and then we'll discuss.
public class JobIdManager { public static final int JOB_TYPE_CHANNEL_PROGRAMS = 1; public static final int JOB_TYPE_CHANNEL_METADATA = 2; public static final int JOB_TYPE_CHANNEL_DELETION = 3; public static final int JOB_TYPE_CHANNEL_LOGGER = 4; public static final int JOB_TYPE_USER_PREFS = 11; public static final int JOB_TYPE_USER_BEHAVIOR = 21; @IntDef(value = { JOB_TYPE_CHANNEL_PROGRAMS, JOB_TYPE_CHANNEL_METADATA, JOB_TYPE_CHANNEL_DELETION, JOB_TYPE_CHANNEL_LOGGER, JOB_TYPE_USER_PREFS, JOB_TYPE_USER_BEHAVIOR }) @Retention(RetentionPolicy.SOURCE) public @interface JobType { } //16-1 for short. Adjust per your needs private static final int JOB_TYPE_SHIFTS = 15; public static int getJobId(@JobType int jobType, int objectId) { if ( 0 < objectId && objectId < (1<< JOB_TYPE_SHIFTS) ) { return (jobType << JOB_TYPE_SHIFTS) + objectId; } else { String err = String.format("objectId %s must be between %s and %s", objectId,0,(1<<JOB_TYPE_SHIFTS)); throw new IllegalArgumentException(err); } } }
As you can see, JobIdManager simply combines a prefix with a channelId to get a jobId. This elegant simplicity, however, is just the tip of the iceberg. Let's consider the assumptions and caveats beneath.
First insight: you must be able to coerce channelId into a Short, so that when you combine channelId with a prefix you still end up with a valid Java Integer. Now of course, strictly speaking, it does not have to be a Short. As long as your prefix and channelId combine into a non-overflowing Integer, it will work. But margin is essential to sound engineering. So unless you truly have no choice, go with a Short coercion. One way you can do this in practice, for objects with large IDs on your remote server, is to define a key in your local database or content provider and use that key to generate your jobIds.
Second insight: your entire app ought to have only one JobIdManager class. That class should generate jobIds for all your app's jobs: whether those jobs have to do with Channels, Users, or Cats and Dogs. The sample JobIdManager class points this out: not all JOB_TYPEs have to do with Channel operations. One job type has to do with user prefs and one with user behavior. The JobIdManager accounts for them all by assigning a different prefix to each job type.
User
Cat
Dog
JOB_TYPE
Third insight: for each -JobService in your app, you must have a unique and final JOB_TYPE_ prefix. Again, this must be an exhaustive one-to-one relationship.
-JobService
JOB_TYPE_
The following code snippet from ChannelProgramsJobService demonstrates how to use a JobIdManager in your project. Whenever you need to schedule a new job, you generate the jobId using JobIdManager.getJobId(...).
JobIdManager.getJobId(...)
import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.PersistableBundle; public class ChannelProgramsJobService extends JobService { private static final String CHANNEL_ID = "channelId"; . . . public static void schedulePeriodicJob(Context context, final int channelId, String channelName, long intervalMillis, long flexMillis) { JobInfo.Builder builder = scheduleJob(context, channelId); builder.setPeriodic(intervalMillis, flexMillis); JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); if (JobScheduler.RESULT_SUCCESS != scheduler.schedule(builder.build())) { //todo what? log to server as analytics maybe? Log.d(TAG, "could not schedule program updates for channel " + channelName); } } private static JobInfo.Builder scheduleJob(Context context,final int channelId){ ComponentName componentName = new ComponentName(context, ChannelProgramsJobService.class); final int jobId = JobIdManager .getJobId(JobIdManager.JOB_TYPE_CHANNEL_PROGRAMS, channelId); PersistableBundle bundle = new PersistableBundle(); bundle.putInt(CHANNEL_ID, channelId); JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName); builder.setPersisted(true); builder.setExtras(bundle); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); return builder; } ... }
Footnote: Thanks to Christopher Tate and Trevor Johns for their invaluable feedback
We're pleased to announce the version 1.0 release of the Android Testing Support Library (ATSL).
ATSL version 1.0 is a major update to our existing testing APIs and comes with lots of new features, improved performance, stability, and bug fixes. It provides full API parity with the now deprecated Android platform testing APIs. This release also adds a number of features that we discussed in our Google I/O 2017 talk, such as native support for Multiprocess Espresso and the Android Test Orchestrator.
We are also happy to announce that, starting with version 1.0, we're distributing releases on Google's Maven repository, which makes it a lot easier to use ATSL in your builds. To learn more about using this repository, see the getting started with the Google Maven repository guide. Note that we're no longer tying future updates to the testing infrastructure with platform updates. If you have not yet upgraded your tests to ATSL, this is an excellent time.
Finally, we want to announce a big update to our Android testing documentation. We've migrated our old testing documentation from our GitHub website to developers.android.com/testing. All the testing documentation now appears in a single place, making it even easier to learn how to write and execute tests on Android.
Let's move on to the fun part of this post, an overview of new APIs and tools that we're providing in this release.
Espresso 3.0.0 comes with amazing new features and improved overall performance. Some of the highlights include: Multiprocess Espresso, Idling Registry and new Idling Resources. Let's dive in and have a more detailed look at these new features:
Multiprocess Espresso
Starting with Android O, the platform includes support for instrumenting tests outside of your app's default process. (Prior to Android O, you could only test against app components in your app's default process.) Multiprocess Espresso makes this support possible. It allows you to seamlessly test your app's UI interactions that cross process boundaries while still maintaining Espresso's synchronization guarantees.
The good news is that Espresso does all the work; you don't have to change anything for setups with UI in multiple processes. You can keep writing your Espresso tests like you would for single process apps, and Espresso automatically handles the process IPC and synchronization between processes.
The following diagram shows how multiple instances of Espresso communicate with each other:
If you want to learn more about Multiprocess Espresso and how to use it, please take a look at our documentation and our Multiprocess sample.
Some apps use build flavors in Gradle or a dependency injection framework, like Dagger, to generate test build configurations that register idling resources. Others simply expose the idling resource through their activities. The problem with all these approaches is that they add complexity to your development workflow, and some of them even break encapsulation. With the latest release of Espresso, we've made it easier to register idling resources from within your app code by introducing the new IdlingRegistry API. IdlingRegistry is a lightweight registry that doesn't bring in the entire Espresso library, so you can more easily register resources from your application code. When combining this API with Multiprocess Espresso, you can register idling resources from any process within your application code.
IdlingRegistry
Registration from the Espresso class is now deprecated.
Writing custom idling resources can be time consuming, so Espresso 3.0.0 now comes with more idling resources out of the box to synchronize your threads. The new resources include: IdlingThreadPoolExecutor and IdlingScheduledThreadPoolExecutor. There will be more to come!
IdlingThreadPoolExecutor
IdlingScheduledThreadPoolExecutor
To take advantage of the new idling resource, add these new dependencies to your build.gradle file:
androidTestCompile "com.android.support.test.espresso.idling:idling-concurrent:3.0.0"
Additionally, CountingIdlingResource, which was previously deprecated in Espresso contrib, has been removed with this release. Therefore, you need to update your tests to use the new CountingIdlingResource package that's located in Espresso idling resource. For the full migration details, refer to our release notes.
CountingIdlingResource
When you test ContentProvider objects, you can now use ProviderTestRule instead of ProviderTestCase2. ProviderTestRule offers an easier way to work with other test rules currently available for AndroidJUnit4.
ContentProvider
ProviderTestRule
ProviderTestCase2
ProviderTestRule includes APIs for initialization, as well as commands to run against a ContentProvider under test. If your ContentProvider is based off of a SQLite database, you can use the ProviderTestRule commands for setting the database file and initialization commands.
To learn more, see the ProviderTestRule documentation.
Android M (API level 23) allows apps to request permissions at runtime. However, the dialogs that request runtime permissions place tests in a state where they cannot continue, causing them to fail. By using GrantPermissionRule, you can skip the dialog popups altogether and simulate a user granting a runtime permission for your app.
GrantPermissionRule
Typically, AndroidJUnitRunner runs all tests in the same instrumentation process, which can cause a number of problems. For example, tests share their state in memory, and if one test crashes, it prevents the remainder of the test suite from running.
Although it's possible to isolate tests by issuing sequential adb commands, this process adds host-side processing load. By using the new Android Test Orchestrator instead, you can achieve test isolation entirely on the device, as shown in this diagram:
adb
Be aware that if your tests require shared state to pass, the orchestrator causes them to fail. This behavior is by design. As of this post, Android Test Orchestrator is in beta and is available for use via the command line. We have integrations planned for Firebase Test Lab and Android Studio, coming soon.
For more information, see the Android Testing Orchestrator developer guide.
AndroidJUnitRunner now includes a number of additional features:
JUnitParams
Sometimes you want to test an activity that you create and configure on the fly as part of your test workflow. Now, you can configure MonitoringInstrumentation (and by extension, AndroidJUnitRunner) using an InterceptingActivityFactory. You can create your activity under test with a test-specific configuration without having to rely on compile-time injection.
MonitoringInstrumentation
AndroidJUnitRunner
InterceptingActivityFactory
This overview highlights only some of the most significant changes that we've made to ATSL. They are many more changes that are worth exploring. For the full release details, refer to our release notes.
Last but not least, we want to thank all the developers who contributed features to this release. We also want to thank the Android testing experts on the mobile engineering teams at American Express, Slack and GDE Chiu-Ki Chan for collaborating with us and providing valuable feedback on the pre-release version of Android Testing Support Library.
Happy testing from the ATSL team!
Android TV brings rich app experiences and entertainment to the biggest screen in your house, and with Android O, we’re making it even easier for users to access content from their favorite apps. We’ve built a new, content-centric home screen experience for Android TV, and we're bringing the Google Assistant to the platform as well. These features put content that users want to access a few clicks, or spoken words, away.
The new Android TV home screen organizes video content into channels and programs in a way that’s familiar to TV viewers. Each Android TV app can publish multiple channels, which are represented as rows of programs on the home screen. Apps add relevant programs on each channel, and update these programs and channels as users access content or when new content is available. To help engage users, programs can include a video preview, which is automatically played when a user focuses on a program. Users can configure which channels they wish to see on the home screen, and the ordering of channels, so the themes and shows they’re interested in are quick and easy to access.
In addition to channels for you app, the top of the new Android TV home screen includes a quick launch bar for users' favorite apps, and a special Watch Next channel. This channel contains programs based on the viewing habits of the user.
The APIs for creating and maintaining channels and programs are part of the TvProvider APIs, which are distributed as an Android Support Library module with Android O. To get started using these APIs, visit the Android O Developer Preview site for an overview, and try out the Android TV Channels and Programs codelab for a first-hand experience building an Android TV app for Android O.
Later this year, Nexus Players will receive the new Android TV home experience as an OTA update. If you wish build and test apps for the new interface today, however, you can use the Android TV emulator or Nexus Player device images that are part of the latest Android O Developer Preview.
The Google Assistant on Android TV, coming later this year, will allow users to quickly find and access content using their voice. Because the Assistant is context-aware, it can help users narrow down what content to play. Users will also be able access the Assistant to control playback, even while a video or music is playing. And since the Assistant can control compatible smart home devices, a simple voice request can dim the lights to create an ideal movie viewing environment. When the Google Assistant comes to Android TV, it will launch in the US on Android devices running M, N, and O.
We're looking forward to seeing how developers take advantage of the new Android TV home screen. We welcome feedback, so please visit the Android TV Developer Community on G+ to share you thoughts and ideas!