Merge "leanback: ArrayObjectAdapter.setItems() with no DiffCallback" into oc-mr1-dev
am: 0ebd974f8f
Change-Id: Ib2e6b4e46cab80eb66497a89db34225cfd94fc3e
diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
index 8f60355..c4b3925 100644
--- a/app-toolkit/init.gradle
+++ b/app-toolkit/init.gradle
@@ -98,7 +98,8 @@
"lifecycle" : "android.arch.lifecycle",
"arch" : "android.arch.core",
"paging" : "android.arch.paging",
- "navigation" : "android.arch.navigation"]
+ "navigation" : "android.arch.navigation",
+ "background" : "android.arch.background.workmanager"]
subprojects {
repos.addMavenRepositories(project.repositories)
@@ -116,7 +117,7 @@
def finalGroup = rootProject.flatfootProjectGroups[mavenGroup]
if (finalGroup == null) {
- return
+ throw new GradleException("bad finalGroup for $project with $project.version")
}
if (projectPath.size() == 2) {// root project.
return
diff --git a/app-toolkit/settings.gradle b/app-toolkit/settings.gradle
index aa0db94..c6fa65d 100644
--- a/app-toolkit/settings.gradle
+++ b/app-toolkit/settings.gradle
@@ -96,6 +96,9 @@
include ':room:integration-tests:kotlintestapp'
project(':room:integration-tests:kotlintestapp').projectDir = new File(supportRoot, "room/integration-tests/kotlintestapp")
+include ':background:workmanager'
+project(':background:workmanager').projectDir = new File(supportRoot, "background/workmanager")
+
/////////////////////////////
//
// SupportLib
diff --git a/background/workmanager/build.gradle b/background/workmanager/build.gradle
new file mode 100644
index 0000000..8d2e6db
--- /dev/null
+++ b/background/workmanager/build.gradle
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+apply plugin: android.support.FlatfootAndroidLibraryPlugin
+
+android {
+ compileSdkVersion tools.current_sdk
+ buildToolsVersion tools.build_tools_version
+
+ defaultConfig {
+ minSdkVersion flatfoot.min_sdk
+ targetSdkVersion tools.current_sdk
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes.all {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ api project(':lifecycle:extensions')
+ api project(':room:runtime')
+ annotationProcessor project(":room:compiler")
+ androidTestImplementation libs.test_runner, { exclude module: 'support-annotations' }
+ androidTestImplementation libs.espresso_core, { exclude module: 'support-annotations' }
+ androidTestImplementation libs.mockito_core, { exclude group: 'net.bytebuddy' }
+ // DexMaker has it"s own MockMaker
+ androidTestImplementation libs.dexmaker_mockito, { exclude group: 'net.bytebuddy' }
+ // DexMaker has it"s own MockMaker
+ testCompile libs.junit
+}
+
+createAndroidCheckstyle(project)
+
+version = LibraryVersions.WORKMANAGER.toString()
+
+supportLibrary {
+ name 'Android Support Library Background WorkManager'
+ publish true
+ inceptionYear '2017'
+ description "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren\'t a part of the framework APIs. Compatible on devices running API 14 or later."
+ url SupportLibraryExtension.ARCHITECTURE_URL
+}
+
diff --git a/background/workmanager/proguard-rules.pro b/background/workmanager/proguard-rules.pro
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/background/workmanager/proguard-rules.pro
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/JobSchedulerConverterTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/JobSchedulerConverterTest.java
new file mode 100644
index 0000000..c1f779e
--- /dev/null
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/JobSchedulerConverterTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.job.JobInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class JobSchedulerConverterTest {
+ private JobSchedulerConverter mConverter;
+
+ @Before
+ public void setUp() {
+ mConverter = new JobSchedulerConverter(InstrumentationRegistry.getTargetContext());
+ }
+
+ @Test
+ public void convert() {
+ String uuid = "026e3422-9cd1-11e7-abc4-cec278b6b50a";
+ // TODO(janclarin): Use int mapping for UUID from DB.
+ int expectedIntId = JobSchedulerConverter.generateJobId(uuid);
+ WorkSpec workSpec = new WorkSpec(uuid);
+ JobInfo jobInfo = mConverter.convert(workSpec);
+ assertEquals(expectedIntId, jobInfo.getId());
+ }
+
+ @Test
+ public void convertWithConstraints() {
+ @Constraints.NetworkType int workSpecNetworkType = Constraints.NETWORK_TYPE_UNMETERED;
+ Constraints expectedConstraints = new Constraints.Builder()
+ .setInitialDelay(12345)
+ .setRequiredNetworkType(workSpecNetworkType)
+ .setRequiresCharging(true)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setRequiresStorageNotLow(true)
+ .build();
+ WorkSpec workSpec = new WorkSpec("id");
+ workSpec.mConstraints = expectedConstraints;
+ JobInfo jobInfo = mConverter.convert(workSpec);
+
+ int expectedNetworkType = mConverter.convertNetworkType(workSpecNetworkType);
+ assertEquals(expectedNetworkType, jobInfo.getNetworkType());
+ assertEquals(expectedConstraints.mInitialDelay, jobInfo.getMinLatencyMillis());
+ assertEquals(expectedConstraints.mRequiresCharging, jobInfo.isRequireCharging());
+ assertEquals(expectedConstraints.mRequiresDeviceIdle, jobInfo.isRequireDeviceIdle());
+ assertEquals(expectedConstraints.mRequiresBatteryNotLow, jobInfo.isRequireBatteryNotLow());
+ assertEquals(expectedConstraints.mRequiresStorageNotLow, jobInfo.isRequireStorageNotLow());
+ }
+
+ @Test
+ public void convertNetworkTypeAny() {
+ convertNetworkTypeHelper(Constraints.NETWORK_TYPE_ANY, JobInfo.NETWORK_TYPE_NONE);
+ }
+
+ @Test
+ public void convertNetworkTypeConnected() {
+ convertNetworkTypeHelper(Constraints.NETWORK_TYPE_CONNECTED, JobInfo.NETWORK_TYPE_ANY);
+ }
+
+ @Test
+ public void convertNetworkTypeUnmetered() {
+ convertNetworkTypeHelper(
+ Constraints.NETWORK_TYPE_UNMETERED, JobInfo.NETWORK_TYPE_UNMETERED);
+ }
+
+ @Test
+ public void convertNetworkTypeNotRoaming() {
+ convertNetworkTypeHelper(
+ Constraints.NETWORK_TYPE_NOT_ROAMING, JobInfo.NETWORK_TYPE_NOT_ROAMING);
+ }
+
+ @Test
+ public void convertNetworkTypeMetered() {
+ convertNetworkTypeHelper(Constraints.NETWORK_TYPE_METERED, JobInfo.NETWORK_TYPE_METERED);
+ }
+
+ private void convertNetworkTypeHelper(@Constraints.NetworkType int constraintNetworkType,
+ int expectedJobInfoNetworkType) {
+ int convertedNetworkType = mConverter.convertNetworkType(constraintNetworkType);
+ assertEquals(expectedJobInfoNetworkType, convertedNetworkType);
+ }
+}
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/TestWorker.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/TestWorker.java
new file mode 100644
index 0000000..dd1f7b3
--- /dev/null
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/TestWorker.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.content.Context;
+
+/**
+ * Simple Test Worker
+ */
+
+public class TestWorker extends Worker<String> {
+ public TestWorker(Context appContext, WorkDatabase workDatabase, WorkSpec workSpec) {
+ super(appContext, workDatabase, workSpec);
+ }
+
+ @Override
+ public String doWork() {
+ return mAppContext.getPackageName();
+ }
+}
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/WorkDatabaseTests.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/WorkDatabaseTests.java
new file mode 100644
index 0000000..f857dff
--- /dev/null
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/WorkDatabaseTests.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WorkDatabaseTests {
+ private WorkDatabase mDatabase;
+ private WorkManager mWorkManager;
+
+ @Before
+ public void setUp() {
+ mDatabase = WorkDatabase.getInMemoryInstance(InstrumentationRegistry.getTargetContext());
+ mWorkManager = new WorkManager.Builder("test")
+ .build(InstrumentationRegistry.getTargetContext());
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void insert() throws InterruptedException, ExecutionException, TimeoutException {
+ final int workCount = 3;
+ final Work[] workArray = new Work[workCount];
+ for (int i = 0; i < workCount; ++i) {
+ workArray[i] = new Work.Builder(TestWorker.class).build();
+ }
+ mWorkManager.enqueue(workArray[0]).then(workArray[1]).then(workArray[2]);
+ Thread.sleep(5000);
+
+ for (int i = 0; i < workCount; ++i) {
+ String id = workArray[i].getId();
+ assertNotNull(mDatabase.workSpecDao().getWorkSpec(id));
+ assertEquals(mDatabase.dependencyDao().hasDependencies(id), (i > 0));
+ }
+ }
+
+ @Test
+ public void constraints() throws InterruptedException, ExecutionException, TimeoutException {
+ Work work0 = new Work.Builder(TestWorker.class)
+ .withConstraints(
+ new Constraints.Builder()
+ .setRequiresCharging(true)
+ .setRequiresDeviceIdle(true)
+ .setRequiredNetworkType(Constraints.NETWORK_TYPE_METERED)
+ .setRequiresBatteryNotLow(true)
+ .setRequiresStorageNotLow(true)
+ .setInitialDelay(5000)
+ .build())
+ .build();
+ Work work1 = new Work.Builder(TestWorker.class).build();
+ mWorkManager.enqueue(work0).then(work1);
+ Thread.sleep(5000);
+
+ WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getId());
+ WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getId());
+
+ assertNotNull(workSpec0.mConstraints);
+ assertTrue(workSpec0.mConstraints.mRequiresCharging);
+ assertTrue(workSpec0.mConstraints.mRequiresDeviceIdle);
+ assertTrue(workSpec0.mConstraints.mRequiresBatteryNotLow);
+ assertTrue(workSpec0.mConstraints.mRequiresStorageNotLow);
+ assertEquals(5000, workSpec0.mConstraints.mInitialDelay);
+ assertEquals(Constraints.NETWORK_TYPE_METERED, workSpec0.mConstraints.mRequiredNetworkType);
+
+ assertNotNull(workSpec1.mConstraints);
+ assertFalse(workSpec1.mConstraints.mRequiresCharging);
+ assertFalse(workSpec1.mConstraints.mRequiresDeviceIdle);
+ assertFalse(workSpec1.mConstraints.mRequiresBatteryNotLow);
+ assertFalse(workSpec1.mConstraints.mRequiresStorageNotLow);
+ assertEquals(0, workSpec1.mConstraints.mInitialDelay);
+ assertEquals(Constraints.NETWORK_TYPE_ANY, workSpec1.mConstraints.mRequiredNetworkType);
+ }
+
+ @Test
+ public void backoffPolicy() throws InterruptedException, ExecutionException, TimeoutException {
+ Work work0 = new Work.Builder(TestWorker.class)
+ .withBackoffCriteria(Work.BACKOFF_POLICY_LINEAR, 50000)
+ .build();
+ Work work1 = new Work.Builder(TestWorker.class).build();
+ mWorkManager.enqueue(work0).then(work1);
+ Thread.sleep(5000);
+
+ WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getId());
+ WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getId());
+
+ assertEquals(Work.BACKOFF_POLICY_LINEAR, workSpec0.mBackoffPolicy);
+ assertEquals(50000, workSpec0.mBackoffDelayDuration);
+
+ assertEquals(Work.BACKOFF_POLICY_EXPONENTIAL, workSpec1.mBackoffPolicy);
+ assertEquals(Work.DEFAULT_BACKOFF_DELAY_DURATION, workSpec1.mBackoffDelayDuration);
+ }
+
+ @Test
+ public void arguments() throws InterruptedException, ExecutionException, TimeoutException {
+ String key = "key";
+ String expectedValue = "value";
+
+ Arguments args = new Arguments();
+ args.putString(key, expectedValue);
+
+ Work work0 = new Work.Builder(TestWorker.class)
+ .withArguments(args)
+ .build();
+ Work work1 = new Work.Builder(TestWorker.class).build();
+ mWorkManager.enqueue(work0).then(work1);
+ Thread.sleep(5000);
+
+ WorkSpec workSpec0 = mDatabase.workSpecDao().getWorkSpec(work0.getId());
+ WorkSpec workSpec1 = mDatabase.workSpecDao().getWorkSpec(work1.getId());
+
+ assertNotNull(workSpec0.mArguments);
+ assertNotNull(workSpec1.mArguments);
+
+ assertEquals(1, workSpec0.mArguments.size());
+ assertEquals(0, workSpec1.mArguments.size());
+
+ String actualValue = workSpec0.mArguments.getString(key, null);
+ assertNotNull(actualValue);
+ assertEquals(expectedValue, actualValue);
+ }
+}
diff --git a/background/workmanager/src/main/AndroidManifest.xml b/background/workmanager/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a0f8c7b
--- /dev/null
+++ b/background/workmanager/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.arch.background.workmanager">
+ <application>
+ <meta-data android:name="android.arch.background.workmanager.VERSION"
+ android:value="${version}" />
+ <service
+ android:name=".WorkService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Arguments.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Arguments.java
new file mode 100644
index 0000000..a47928a
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Arguments.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Persistable set of Key/Value pairs which are passed to each {@link Worker}.
+ */
+
+public final class Arguments {
+ private Map<String, Object> mValues;
+
+ public Arguments() {
+ mValues = new HashMap<>();
+ }
+
+ Arguments(Map<? extends String, ?> values) {
+ mValues = new HashMap<>(values);
+ }
+
+ /**
+ * Insert boolean into arguments.
+ *
+ * @param key String
+ * @param value boolean
+ */
+ public void putBoolean(String key, boolean value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get boolean matching key from arguments. If not found, use default value specified.
+ *
+ * @param key String
+ * @return boolean
+ */
+ public boolean getBoolean(String key, boolean defaultValue) {
+ Object value = mValues.get(key);
+ if (value instanceof Boolean) {
+ return (Boolean) value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Insert int into arguments.
+ *
+ * @param key String
+ * @param value int
+ */
+ public void putInt(String key, int value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get int matching key from arguments. If not found, use default value specified.
+ *
+ * @param key String
+ * @return int
+ */
+ public int getInt(String key, int defaultValue) {
+ Object value = mValues.get(key);
+ if (value instanceof Integer) {
+ return (Integer) value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Insert int array into arguments.
+ *
+ * @param key String
+ * @param value int array
+ */
+ public void putIntArray(String key, int[] value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get int array matching key from arguments. If not found, return null.
+ *
+ * @param key String
+ * @return int array
+ */
+ public int[] getIntArray(String key) {
+ Object value = mValues.get(key);
+ if (value instanceof int[]) {
+ return (int[]) value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Insert long into arguments.
+ *
+ * @param key String
+ * @param value long
+ */
+ public void putLong(String key, long value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get long matching key from arguments. If not found, use default value specified.
+ *
+ * @param key String
+ * @return long
+ */
+ public long getLong(String key, long defaultValue) {
+ Object value = mValues.get(key);
+ if (value instanceof Long) {
+ return (Long) value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Insert long array into arguments.
+ *
+ * @param key String
+ * @param value long array
+ */
+ public void putLongArray(String key, long[] value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get long array matching key from arguments. If not found, return null.
+ *
+ * @param key String
+ * @return long array
+ */
+ public long[] getLongArray(String key) {
+ Object value = mValues.get(key);
+ if (value instanceof long[]) {
+ return (long[]) value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Insert double into arguments.
+ *
+ * @param key String
+ * @param value double
+ */
+ public void putDouble(String key, double value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get double matching key from arguments. If not found, use default value specified.
+ *
+ * @param key String
+ * @return long array
+ */
+ public double getDouble(String key, double defaultValue) {
+ Object value = mValues.get(key);
+ if (value instanceof Double) {
+ return (Double) value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Insert double array into arguments.
+ *
+ * @param key String
+ * @param value double array
+ */
+ public void putDoubleArray(String key, double[] value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get double array matching key from arguments. If not found, return null.
+ *
+ * @param key String
+ * @return double array
+ */
+ public double[] getDoubleArray(String key) {
+ Object value = mValues.get(key);
+ if (value instanceof double[]) {
+ return (double[]) value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Insert String into arguments.
+ *
+ * @param key String
+ * @param value String
+ */
+ public void putString(String key, String value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get String matching key from arguments. If not found, use default value specified.
+ *
+ * @param key String
+ * @return String
+ */
+ public String getString(String key, String defaultValue) {
+ Object value = mValues.get(key);
+ if (value instanceof String) {
+ return (String) value;
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Insert String array into arguments.
+ *
+ * @param key String
+ * @param value String array
+ */
+ public void putStringArray(String key, String[] value) {
+ mValues.put(key, value);
+ }
+
+ /**
+ * Get String array matching key from arguments. If not found, return null.
+ *
+ * @param key String
+ * @return String
+ */
+ public String[] getStringArray(String key) {
+ Object value = mValues.get(key);
+ if (value instanceof String[]) {
+ return (String[]) value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Clears all arguments.
+ */
+ public void clear() {
+ mValues.clear();
+ }
+
+ /**
+ * Determine if key is present in arguments.
+ *
+ * @param key String
+ * @return true if key is present, false otherwise
+ */
+ public boolean containsKey(String key) {
+ return mValues.containsKey(key);
+ }
+
+ /**
+ * Determine if arguments are empty.
+ *
+ * @return true if arguments are empty, false otherwise
+ */
+ public boolean isEmpty() {
+ return mValues.isEmpty();
+ }
+
+ /**
+ * Get set of keys for arguments.
+ *
+ * @return Set<String> of keys
+ */
+ public Set<String> keySet() {
+ return mValues.keySet();
+ }
+
+ /**
+ * Removes a key/value pair from arguments.
+ *
+ * @param key String
+ */
+ public void remove(String key) {
+ mValues.remove(key);
+ }
+
+ /**
+ * Get number of arguments.
+ *
+ * @return int
+ */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * Get set of key/value pairs for arguments.
+ *
+ * @return Set<Entry> of keys
+ */
+ public Set<Map.Entry<String, Object>> entrySet() {
+ return mValues.entrySet();
+ }
+
+ /**
+ * Converts {@link Arguments} to Byte Array for persistent storage.
+ *
+ * @param arguments {@link Arguments} object to convert
+ * @return byte array representation
+ */
+ @TypeConverter
+ public static byte[] toByteArray(Arguments arguments) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = null;
+ try {
+ objectOutputStream = new ObjectOutputStream(outputStream);
+ objectOutputStream.writeInt(arguments.size());
+ for (Map.Entry<String, Object> entry : arguments.entrySet()) {
+ objectOutputStream.writeUTF(entry.getKey());
+ objectOutputStream.writeObject(entry.getValue());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (objectOutputStream != null) {
+ try {
+ objectOutputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Converts Byte Array to {@link Arguments}.
+ *
+ * @param bytes byte array representation to convert
+ * @return {@link Arguments} object
+ */
+ @TypeConverter
+ public static Arguments fromByteArray(byte[] bytes) {
+ Map<String, Object> map = new HashMap<>();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInputStream objectInputStream = null;
+ try {
+ objectInputStream = new ObjectInputStream(inputStream);
+ for (int i = objectInputStream.readInt(); i > 0; i--) {
+ map.put(objectInputStream.readUTF(), objectInputStream.readObject());
+ }
+ } catch (IOException | ClassNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ if (objectInputStream != null) {
+ try {
+ objectInputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return new Arguments(map);
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Constraints.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Constraints.java
new file mode 100644
index 0000000..de181cc
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Constraints.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * The constraints that can be applied to one {@link WorkSpec}.
+ */
+
+public class Constraints {
+ @Retention(SOURCE)
+ @IntDef({NETWORK_TYPE_CONNECTED, NETWORK_TYPE_METERED, NETWORK_TYPE_ANY,
+ NETWORK_TYPE_NOT_ROAMING, NETWORK_TYPE_UNMETERED})
+ @interface NetworkType {
+ }
+
+ // TODO(xbhatnag): Merge with JobScheduler values.
+ public static final int NETWORK_TYPE_ANY = 0;
+ public static final int NETWORK_TYPE_CONNECTED = 1;
+ public static final int NETWORK_TYPE_UNMETERED = 2;
+ public static final int NETWORK_TYPE_METERED = 3;
+ public static final int NETWORK_TYPE_NOT_ROAMING = 4;
+
+ @NetworkType
+ int mRequiredNetworkType;
+ boolean mRequiresCharging;
+ boolean mRequiresDeviceIdle;
+ boolean mRequiresBatteryNotLow;
+ boolean mRequiresStorageNotLow;
+ long mInitialDelay;
+
+ Constraints() { // stub required for room
+ }
+
+ private Constraints(Builder builder) {
+ mRequiresCharging = builder.mRequiresCharging;
+ mRequiresDeviceIdle = builder.mRequiresDeviceIdle;
+ mRequiredNetworkType = builder.mRequiredNetworkType;
+ mRequiresBatteryNotLow = builder.mRequiresBatteryNotLow;
+ mRequiresStorageNotLow = builder.mRequiresStorageNotLow;
+ mInitialDelay = builder.mInitialDelay;
+ }
+
+ /**
+ * Builder for {@link Constraints} class.
+ */
+ public static class Builder {
+ private boolean mRequiresCharging = false;
+ private boolean mRequiresDeviceIdle = false;
+ private int mRequiredNetworkType = NETWORK_TYPE_ANY;
+ private boolean mRequiresBatteryNotLow = false;
+ private boolean mRequiresStorageNotLow = false;
+ private long mInitialDelay = 0L;
+
+ /**
+ * Specify whether device should be plugged in for {@link WorkSpec} to run.
+ * Default is false.
+ *
+ * @param requiresCharging true if device must be plugged in, false otherwise
+ * @return current builder
+ */
+ public Builder setRequiresCharging(boolean requiresCharging) {
+ this.mRequiresCharging = requiresCharging;
+ return this;
+ }
+
+ /**
+ * Specify whether device should be idle for {@link WorkSpec} to run. Default is false.
+ *
+ * @param requiresDeviceIdle true if device must be idle, false otherwise
+ * @return current builder
+ */
+ public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
+ this.mRequiresDeviceIdle = requiresDeviceIdle;
+ return this;
+ }
+
+ /**
+ * Specify whether device should have a particular {@link NetworkType} for {@link WorkSpec}
+ * to run. Default is {@value #NETWORK_TYPE_ANY}
+ *
+ * @param networkType type of network required
+ * @return current builder
+ */
+ public Builder setRequiredNetworkType(@NetworkType int networkType) {
+ this.mRequiredNetworkType = networkType;
+ return this;
+ }
+
+ /**
+ * Specify whether device battery should not be below critical threshold for
+ * {@link WorkSpec} to run. Default is false.
+ *
+ * @param requiresBatteryNotLow true if battery should not be below critical threshold,
+ * false otherwise
+ * @return current builder
+ */
+ public Builder setRequiresBatteryNotLow(boolean requiresBatteryNotLow) {
+ this.mRequiresBatteryNotLow = requiresBatteryNotLow;
+ return this;
+ }
+
+ /**
+ * Specify whether device available storage should not be below critical threshold for
+ * {@link WorkSpec} to run. Default is false.
+ *
+ * @param requiresStorageNotLow true if available storage should not be below critical
+ * threshold, false otherwise
+ * @return current builder
+ */
+ public Builder setRequiresStorageNotLow(boolean requiresStorageNotLow) {
+ this.mRequiresStorageNotLow = requiresStorageNotLow;
+ return this;
+ }
+
+ /**
+ * Specify whether {@link WorkSpec} should run with an initial delay. Default is 0ms.
+ *
+ * @param duration initial delay before running WorkSpec (in milliseconds)
+ * @return current builder
+ */
+ public Builder setInitialDelay(long duration) {
+ // TODO(xbhatnag) : Does this affect rescheduled jobs?
+ this.mInitialDelay = duration;
+ return this;
+ }
+
+ /**
+ * Generates the {@link Constraints} from this Builder.
+ *
+ * @return new {@link Constraints} which can be attached to a {@link WorkSpec}
+ */
+ public Constraints build() {
+ return new Constraints(this);
+ }
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Dependency.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Dependency.java
new file mode 100644
index 0000000..bc0123b
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Dependency.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Index;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * Database entity that defines a dependency between two {@link WorkSpec}s.
+ */
+
+// TODO(xbhatnag): Replace with single foreign key. (b/65681278)
+@Entity(foreignKeys = {
+ @ForeignKey(
+ entity = WorkSpec.class,
+ parentColumns = "id",
+ childColumns = "work_spec_id",
+ onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE),
+ @ForeignKey(
+ entity = WorkSpec.class,
+ parentColumns = "id",
+ childColumns = "prerequisite_id",
+ onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE)},
+ indices = {@Index(value = "work_spec_id"), @Index(value = "prerequisite_id")})
+class Dependency {
+ // Note: Since this is always null, SQLite will auto-increment the primary key id.
+ @ColumnInfo(name = "id")
+ @PrimaryKey
+ Integer mId;
+
+ @ColumnInfo(name = "work_spec_id")
+ String mWorkSpecId;
+
+ @ColumnInfo(name = "prerequisite_id")
+ String mPrerequisiteId;
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/DependencyDao.java b/background/workmanager/src/main/java/android/arch/background/workmanager/DependencyDao.java
new file mode 100644
index 0000000..197ac43
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/DependencyDao.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static android.arch.persistence.room.OnConflictStrategy.FAIL;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+
+/**
+ * The Data Access Object for {@link Dependency}.
+ */
+@Dao
+public interface DependencyDao {
+ /**
+ * Attempts to insert a {@link Dependency} into the database.
+ *
+ * @param dependency The {@link Dependency}s to insert
+ */
+ @Insert(onConflict = FAIL)
+ void insertDependency(Dependency dependency);
+
+ /**
+ * Determines if a {@link WorkSpec is dependent on other {@link WorkSpec}s
+ * that are not in a {@value Work#STATUS_SUCCEEDED} state.
+ *
+ * @param id The identifier for the {@link WorkSpec}
+ * @return true if the {@link WorkSpec} is dependent on other {@link WorkSpec}s
+ */
+ @Query("SELECT COUNT(id) > 0 FROM workspec WHERE status!=2 AND id IN"
+ + "(SELECT prerequisite_id FROM dependency WHERE work_spec_id=:id)")
+ boolean hasDependencies(String id); // TODO: Replace 2 with STATUS_SUCCEEDED constant
+ // TODO: Refactor this method to a separate DAO.
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/JobSchedulerConverter.java b/background/workmanager/src/main/java/android/arch/background/workmanager/JobSchedulerConverter.java
new file mode 100644
index 0000000..adcd57c
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/JobSchedulerConverter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+
+/**
+ * Converts a {@link WorkSpec} into a JobInfo.
+ */
+@RequiresApi(api = 21)
+class JobSchedulerConverter implements WorkSpecConverter<JobInfo> {
+ private static final String TAG = "JobSchedulerConverter";
+
+ private final ComponentName mWorkServiceComponent;
+
+ JobSchedulerConverter(@NonNull Context context) {
+ mWorkServiceComponent = new ComponentName(context, WorkService.class);
+ }
+
+ @Override
+ public JobInfo convert(WorkSpec workSpec) {
+ Constraints constraints = workSpec.mConstraints;
+ int jobId = generateJobId(workSpec.mId);
+ int jobNetworkType = convertNetworkType(constraints.mRequiredNetworkType);
+ JobInfo.Builder builder =
+ new JobInfo.Builder(jobId, mWorkServiceComponent)
+ .setMinimumLatency(constraints.mInitialDelay)
+ .setRequiredNetworkType(jobNetworkType)
+ .setRequiresCharging(constraints.mRequiresCharging)
+ .setRequiresDeviceIdle(constraints.mRequiresDeviceIdle);
+
+ if (Build.VERSION.SDK_INT >= 26) {
+ builder.setRequiresBatteryNotLow(constraints.mRequiresBatteryNotLow);
+ builder.setRequiresStorageNotLow(constraints.mRequiresStorageNotLow);
+ } else {
+ // TODO(janclarin): Create compat version of batteryNotLow/storageNotLow constraints.
+ Log.w(TAG, "Could not set requiresBatteryNowLow or requiresStorageNotLow constraints.");
+ }
+ return builder.build();
+ }
+
+ @Override
+ public int convertNetworkType(@Constraints.NetworkType int networkType)
+ throws IllegalArgumentException {
+ switch(networkType) {
+ case Constraints.NETWORK_TYPE_ANY:
+ return JobInfo.NETWORK_TYPE_NONE;
+ case Constraints.NETWORK_TYPE_CONNECTED:
+ return JobInfo.NETWORK_TYPE_ANY;
+ case Constraints.NETWORK_TYPE_UNMETERED:
+ return JobInfo.NETWORK_TYPE_UNMETERED;
+ case Constraints.NETWORK_TYPE_NOT_ROAMING:
+ if (Build.VERSION.SDK_INT >= 24) {
+ return JobInfo.NETWORK_TYPE_NOT_ROAMING;
+ }
+ break;
+ case Constraints.NETWORK_TYPE_METERED:
+ if (Build.VERSION.SDK_INT >= 26) {
+ return JobInfo.NETWORK_TYPE_METERED;
+ }
+ break;
+ }
+ throw new IllegalArgumentException("NetworkType of " + networkType + " is not supported.");
+ }
+
+ // TODO(janclarin): Store UUID mapping with incrementing integer work ID.
+ static int generateJobId(String uuid) {
+ return uuid.hashCode();
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Scheduler.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Scheduler.java
new file mode 100644
index 0000000..5d1fbd3
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Scheduler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+/**
+ * Schedules {@link Work} depending on completion of {@link Dependency}s.
+ */
+abstract class Scheduler {
+ // TODO(janclarin): Listen for completion of works via ID and schedule jobs dependent on it.
+
+ /**
+ * @param workId ID of {@link Work} to schedule.
+ */
+ abstract void schedule(String workId);
+
+ /**
+ * @param workId ID of {@link Work} to cancel.
+ * @return boolean indicating if the work was canceled.
+ */
+ abstract boolean cancel(String workId);
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/SchedulerHelper.java b/background/workmanager/src/main/java/android/arch/background/workmanager/SchedulerHelper.java
new file mode 100644
index 0000000..1d5d7d7
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/SchedulerHelper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+/**
+ * Returns the proper {@link Scheduler} based on constraints.
+ */
+class SchedulerHelper {
+ private SchedulerHelper() {}
+
+ /**
+ * @return {@link Scheduler} based on constraints.
+ */
+ static Scheduler getScheduler() {
+ // TODO(janclarin): Determine criteria to pass in and return Scheduler.
+ return null;
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Work.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Work.java
new file mode 100644
index 0000000..32ddf3d
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Work.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.util.UUID;
+
+/**
+ * A class to create a logical unit of work.
+ */
+
+public class Work {
+
+ @Retention(SOURCE)
+ @IntDef({STATUS_FAILED, STATUS_RUNNING, STATUS_SUCCEEDED, STATUS_ENQUEUED})
+ public @interface WorkStatus {
+ }
+
+ @Retention(SOURCE)
+ @IntDef({BACKOFF_POLICY_EXPONENTIAL, BACKOFF_POLICY_LINEAR})
+ public @interface BackoffPolicy {
+ }
+
+ public static final int STATUS_ENQUEUED = 0;
+ public static final int STATUS_RUNNING = 1;
+ public static final int STATUS_SUCCEEDED = 2;
+ public static final int STATUS_FAILED = 3;
+
+ public static final int BACKOFF_POLICY_EXPONENTIAL = 0;
+ public static final int BACKOFF_POLICY_LINEAR = 1;
+ public static final long DEFAULT_BACKOFF_DELAY_DURATION = 30000L;
+
+ private WorkSpec mWorkSpec;
+
+ private Work(WorkSpec workSpec) {
+ mWorkSpec = workSpec;
+ }
+
+ /**
+ * @return The id for this set of work.
+ */
+ public String getId() {
+ return mWorkSpec.mId;
+ }
+
+
+ WorkSpec getWorkSpec() {
+ return mWorkSpec;
+ }
+
+ /**
+ * Builder for {@link Work} class.
+ */
+ public static class Builder {
+ private WorkSpec mWorkSpec = new WorkSpec(UUID.randomUUID().toString());
+
+ public Builder(Class<? extends Worker> workerClass) {
+ mWorkSpec.mWorkerClassName = workerClass.getName();
+ }
+
+ /**
+ * Add constraints to the {@link Work}.
+ *
+ * @param constraints The constraints for the {@link Work}
+ * @return current builder
+ */
+ public Builder withConstraints(@NonNull Constraints constraints) {
+ mWorkSpec.mConstraints = constraints;
+ return this;
+ }
+
+ /**
+ * Change backoff policy and delay for the {@link Work}.
+ * Default is {@value Work#BACKOFF_POLICY_EXPONENTIAL} and 30 seconds.
+ *
+ * @param backoffPolicy Backoff Policy to use for {@link Work}
+ * @param backoffDelayDuration Time to wait before restarting {@link Worker}
+ * (in milliseconds)
+ * @return current builder
+ */
+ public Builder withBackoffCriteria(@BackoffPolicy int backoffPolicy,
+ long backoffDelayDuration) {
+ // TODO(xbhatnag): Enforce restrictions on backoff delay. 30 seconds?
+ mWorkSpec.mBackoffPolicy = backoffPolicy;
+ mWorkSpec.mBackoffDelayDuration = backoffDelayDuration;
+ return this;
+ }
+
+ /**
+ * Add arguments to the {@link Work}.
+ *
+ * @param arguments key/value pairs that will be provided to the {@link Worker} class
+ * @return current builder
+ */
+ public Builder withArguments(Arguments arguments) {
+ mWorkSpec.mArguments = arguments;
+ return this;
+ }
+
+ /**
+ * Add an optional tag to the {@link Work}. This is particularly useful for modules or
+ * libraries who want to query for or cancel all of their own work.
+ *
+ * @param tag A tag for identifying the {@link Work} in queries.
+ */
+ public Builder withTag(String tag) {
+ mWorkSpec.mTag = tag;
+ return this;
+ }
+
+ /**
+ * Generates the {@link Work} from this builder
+ *
+ * @return new {@link Work}
+ */
+ public Work build() {
+ return new Work(mWorkSpec);
+ }
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkContinuation.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkContinuation.java
new file mode 100644
index 0000000..3556b16
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkContinuation.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.arch.background.workmanager;
+
+/**
+ * An opaque class that allows you to chain together {@link Work}.
+ */
+
+public class WorkContinuation {
+
+ WorkManager mWorkManager;
+ String mPrerequisiteId;
+
+ WorkContinuation(WorkManager workManager, String prerequisiteId) {
+ mWorkManager = workManager;
+ mPrerequisiteId = prerequisiteId;
+ }
+
+ /**
+ * Add new {@link Work} that depends on the previous one.
+ *
+ * @param work The {@link Work} to add
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation then(Work work) {
+ return mWorkManager.enqueue(work, mPrerequisiteId);
+ }
+
+ /**
+ * Add new {@link Work} that depends on the previous one.
+ *
+ * @param workBuilder The {@link Work.Builder} to add; internally {@code build} is called on it
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation then(Work.Builder workBuilder) {
+ return mWorkManager.enqueue(workBuilder.build(), mPrerequisiteId);
+ }
+
+ /**
+ * Add new {@link Work} that depends on the previous one.
+ *
+ * @param workerClass The {@link Worker} to enqueue; this is a convenience method that makes a
+ * {@link Work} object with default arguments using this Worker
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation then(Class<? extends Worker> workerClass) {
+ return mWorkManager.enqueue(new Work.Builder(workerClass).build(), mPrerequisiteId);
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkDatabase.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkDatabase.java
new file mode 100644
index 0000000..af9dc09
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkDatabase.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * A Room database for keeping track of work statuses.
+ */
+@Database(entities = {WorkSpec.class, Dependency.class}, version = 1)
+public abstract class WorkDatabase extends RoomDatabase {
+
+ private static final String DB_NAME_PREFIX = "android.arch.background.workmanager.work.";
+
+ private static WorkDatabase sInstance;
+
+ /**
+ * Returns a static instance of the WorkDatabase.
+ *
+ * @param context A context (this method will use the application context from it)
+ * @param name The database name (will be prefixed by {@code DB_NAME_PREFIX})
+ * @return The singleton WorkDatabase for this process
+ */
+ public static WorkDatabase getInstance(Context context, String name) {
+ if (sInstance == null) {
+ sInstance = Room.databaseBuilder(
+ context.getApplicationContext(),
+ WorkDatabase.class,
+ DB_NAME_PREFIX + name)
+ .build();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Returns an in memory static instance of the WorkDatabase used for testing.
+ *
+ * @param context A context (this method will use the application context from it)
+ * @return The singleton WorkDatabase for this process
+ */
+ @VisibleForTesting
+ static WorkDatabase getInMemoryInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = Room.inMemoryDatabaseBuilder(
+ context.getApplicationContext(),
+ WorkDatabase.class)
+ .build();
+ }
+ return sInstance;
+ }
+
+ /**
+ * @return The Data Access Object for {@link WorkSpec}s.
+ */
+ public abstract WorkSpecDao workSpecDao();
+
+ /**
+ * @return The Data Access Object for {@link Dependency}s.
+ */
+ public abstract DependencyDao dependencyDao();
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkExecutionManager.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkExecutionManager.java
new file mode 100644
index 0000000..2151b1d
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkExecutionManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static android.arch.background.workmanager.Work.STATUS_ENQUEUED;
+import static android.arch.background.workmanager.Work.STATUS_FAILED;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class to manage the actual in-process (foreground) execution of work.
+ */
+class WorkExecutionManager {
+
+ private static final String TAG = "WorkExecMgr";
+
+ private Context mAppContext;
+ private WorkDatabase mWorkDatabase;
+ private ScheduledExecutorService mExecutor;
+
+ private Map<String, Future<?>> mFutures = new HashMap<>();
+ private final Object mLock = new Object();
+
+ WorkExecutionManager(
+ Context context,
+ WorkDatabase workDatabase,
+ ScheduledExecutorService executor) {
+ mAppContext = context.getApplicationContext();
+ mWorkDatabase = workDatabase;
+ mExecutor = executor;
+ }
+
+ void enqueue(String id, long delayMs) {
+ synchronized (mLock) {
+ InternalRunnable runnable = new InternalRunnable(id);
+ Future<?> future = mExecutor.schedule(runnable, delayMs, TimeUnit.MILLISECONDS);
+ mFutures.put(id, future);
+ }
+ }
+
+ boolean cancel(String id) {
+ synchronized (mLock) {
+ Future<?> future = mFutures.get(id);
+ if (future != null) {
+ boolean canceled = future.cancel(true);
+ mFutures.remove(id);
+ return canceled;
+ }
+ }
+ return false;
+ }
+
+ void shutdown() {
+ synchronized (mLock) {
+ for (Future future : mFutures.values()) {
+ if (future != null) {
+ // TODO(sumir): Investigate if we should interrupt running tasks.
+ // Also look at mExecutor.shutdown() vs. mExecutor.shutdownNow()
+ future.cancel(true);
+ }
+ }
+ mFutures.clear();
+ mExecutor.shutdownNow();
+ mExecutor = null;
+ }
+ }
+
+ /**
+ * A callable that looks up the {@link WorkSpec} from the database for a given mId, instantiates
+ * its Worker, and then calls it.
+ */
+ private class InternalRunnable implements Runnable {
+
+ String mId;
+
+ InternalRunnable(String id) {
+ mId = id;
+ }
+
+ @Override
+ public void run() {
+ WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
+ WorkSpec workSpec = workSpecDao.getWorkSpec(mId);
+ if (workSpec != null) {
+ int status = workSpec.mStatus;
+ if (status != STATUS_ENQUEUED) {
+ Log.d(TAG, "Status for " + mId + " is not enqueued; not doing any work");
+ return;
+ }
+
+ Worker worker = Worker.fromWorkSpec(mAppContext, mWorkDatabase, workSpec);
+ if (worker == null) {
+ Log.e(TAG, "Could not create Worker " + workSpec.mWorkerClassName);
+ workSpecDao.setWorkSpecStatus(mId, STATUS_FAILED);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mFutures.get(mId) == null) {
+ Log.d(
+ TAG,
+ "InternalRunnable for id " + mId
+ + " was interrupted; not starting work");
+ return;
+ }
+ }
+
+ worker.call();
+ synchronized (mLock) {
+ mFutures.remove(mId);
+ }
+ } else {
+ Log.e(TAG, "Didn't find WorkSpec for id " + mId);
+ synchronized (mLock) {
+ mFutures.remove(mId);
+ }
+ }
+ }
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkManager.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkManager.java
new file mode 100644
index 0000000..4de587d
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkManager.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.arch.lifecycle.ProcessLifecycleOwner;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * WorkManager is a class used to enqueue persisted work that is guaranteed to run after its
+ * constraints are met.
+ */
+public final class WorkManager implements LifecycleObserver {
+
+ private static final String TAG = "WorkManager";
+
+ private Context mContext;
+ private String mName;
+ private ScheduledExecutorService mForegroundExecutor;
+ private ExecutorService mBackgroundExecutor;
+ private WorkDatabase mWorkDatabase;
+ private ExecutorService mEnqueueExecutor = Executors.newSingleThreadExecutor();
+ private WorkExecutionManager mForegroundWorkExecutionMgr;
+ private WorkSpecConverter<JobInfo> mWorkSpecConverter;
+
+ private WorkManager(
+ Context context,
+ String name,
+ ScheduledExecutorService foregroundExecutor,
+ ExecutorService backgroundExecutor) {
+ mContext = context.getApplicationContext();
+ mName = name;
+ mForegroundExecutor =
+ (foregroundExecutor == null)
+ ? Executors.newScheduledThreadPool(4) // TODO: Configure intelligently.
+ : foregroundExecutor;
+ mBackgroundExecutor =
+ (backgroundExecutor == null)
+ ? Executors.newSingleThreadExecutor() // TODO: Configure intelligently.
+ : backgroundExecutor;
+ mWorkDatabase = WorkDatabase.getInstance(mContext, mName);
+ ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
+
+ // TODO(janclarin): Wrap JobScheduler logic behind another interface.
+ if (Build.VERSION.SDK_INT >= 21) {
+ mWorkSpecConverter = new JobSchedulerConverter(mContext);
+ }
+ }
+
+ /**
+ * Called when the process lifecycle is considered started.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void onLifecycleStart() {
+ mForegroundWorkExecutionMgr = new WorkExecutionManager(
+ mContext,
+ mWorkDatabase,
+ mForegroundExecutor);
+ }
+
+ /**
+ * Called when the process lifecycle is considered stopped.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onLifecycleStop() {
+ mForegroundWorkExecutionMgr.shutdown();
+ mForegroundWorkExecutionMgr = null;
+ }
+
+ /**
+ * Enqueues an item for background processing.
+ *
+ * @param work The {@link Work} to enqueue
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation enqueue(Work work) {
+ return enqueue(work, null);
+ }
+
+ /**
+ * Enqueues an item for background processing.
+ *
+ * @param workBuilder The {@link Work.Builder} to enqueue; internally {@code build} is called
+ * on it
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation enqueue(Work.Builder workBuilder) {
+ return enqueue(workBuilder.build(), null);
+ }
+
+ /**
+ * Enqueues an item for background processing.
+ *
+ * @param workerClass The {@link Worker} to enqueue; this is a convenience method that makes a
+ * {@link Work} object with default arguments using this Worker
+ * @return A {@link WorkContinuation} that allows further chaining
+ */
+ public WorkContinuation enqueue(Class<? extends Worker> workerClass) {
+ return enqueue(new Work.Builder(workerClass).build(), null);
+ }
+
+ WorkContinuation enqueue(Work work, String prerequisiteId) {
+ WorkContinuation workContinuation = new WorkContinuation(this, work.getId());
+ mEnqueueExecutor.execute(new EnqueueRunnable(work, prerequisiteId));
+ return workContinuation;
+ }
+
+ /**
+ * A Runnable to enqueue a {@link Work} in the database.
+ */
+ private class EnqueueRunnable implements Runnable {
+ private Work mWork;
+ private String mPrerequisiteId;
+
+ EnqueueRunnable(Work work, String prerequisiteId) {
+ mWork = work;
+ mPrerequisiteId = prerequisiteId;
+ }
+
+ @Override
+ public void run() {
+ mWorkDatabase.beginTransaction();
+ try {
+ mWorkDatabase.workSpecDao().insertWorkSpec(mWork.getWorkSpec());
+ if (mPrerequisiteId != null) {
+ Dependency dep = new Dependency();
+ dep.mPrerequisiteId = mPrerequisiteId;
+ dep.mWorkSpecId = mWork.getId();
+ mWorkDatabase.dependencyDao().insertDependency(dep);
+ } else {
+ if (mForegroundWorkExecutionMgr != null) {
+ mForegroundWorkExecutionMgr.enqueue(
+ mWork.getId(),
+ 0L /* TODO: delay */);
+ // TODO: Schedule dependent work.
+ }
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ scheduleWorkWithJobScheduler();
+ // TODO(janclarin): Schedule dependent work.
+ }
+ }
+
+ mWorkDatabase.setTransactionSuccessful();
+ } finally {
+ mWorkDatabase.endTransaction();
+ }
+ }
+
+ @RequiresApi(api = 21)
+ private void scheduleWorkWithJobScheduler() {
+ JobScheduler jobScheduler =
+ (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ if (jobScheduler != null) {
+ JobInfo jobInfo = mWorkSpecConverter.convert(mWork.getWorkSpec());
+ jobScheduler.schedule(jobInfo);
+ }
+ }
+ }
+
+ /**
+ * A Builder for {@link WorkManager}.
+ */
+ public static class Builder {
+
+ private String mName;
+ private ScheduledExecutorService mForegroundExecutor;
+ private ExecutorService mBackgroundExecutor;
+
+ public Builder(String name) {
+ mName = name;
+ }
+
+ /**
+ * @param foregroundExecutor The ExecutorService to run in-process during active lifecycles
+ * @return The Builder
+ */
+ public Builder withForegroundExecutor(ScheduledExecutorService foregroundExecutor) {
+ mForegroundExecutor = foregroundExecutor;
+ return this;
+ }
+
+ /**
+ * @param backgroundExecutor The ExecutorService to run via OS-defined background execution
+ * such as {@link android.app.job.JobScheduler}
+ * @return The Builder
+ */
+ public Builder withBackgroundExecutor(ExecutorService backgroundExecutor) {
+ mBackgroundExecutor = backgroundExecutor;
+ return this;
+ }
+
+ /**
+ * Builds the {@link WorkManager}.
+ *
+ * @param context The context used for initialization (we will get the Application context)
+ * @return The {@link WorkManager}
+ */
+ public WorkManager build(Context context) {
+ return new WorkManager(context, mName, mForegroundExecutor, mBackgroundExecutor);
+ }
+ }
+}
+
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkService.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkService.java
new file mode 100644
index 0000000..48c5aac
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Log;
+
+/**
+ * Service invoked by {@link android.app.job.JobScheduler} to run work tasks.
+ */
+@TargetApi(21)
+public class WorkService extends JobService {
+
+ private static final String TAG = "WorkService";
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ int jobId = params.getJobId();
+ Log.d(TAG, jobId + " scheduled on JobScheduler");
+ // TODO(janclarin): Schedule work with instance of WorkExecutionManager.
+ // TODO(janclarin): Call jobFinished after task is completed.
+ jobFinished(params, false);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ int jobId = params.getJobId();
+ // TODO(janclarin): Cancel work with instance of WorkExecutionManager.
+ Log.d(TAG, jobId + " stopped");
+ return false;
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpec.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpec.java
new file mode 100644
index 0000000..17cf1f6
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpec.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.TypeConverters;
+
+/**
+ * Stores information about a logical unit of work.
+ */
+@Entity
+@TypeConverters(Arguments.class)
+public class WorkSpec {
+
+ @ColumnInfo(name = "id")
+ @PrimaryKey
+ String mId;
+
+ // TODO(xbhatnag)
+ @ColumnInfo(name = "repeat_duration")
+ long mRepeatDuration;
+
+ // TODO(xbhatnag)
+ @ColumnInfo(name = "flex_duration")
+ long mFlexDuration;
+
+ @ColumnInfo(name = "status")
+ @Work.WorkStatus
+ int mStatus = Work.STATUS_ENQUEUED;
+
+ @ColumnInfo(name = "worker_class_name")
+ String mWorkerClassName;
+
+ @Embedded
+ Constraints mConstraints = new Constraints.Builder().build();
+
+ Arguments mArguments = new Arguments();
+
+ String mTag;
+
+ // TODO(sumir): Should Backoff be disabled by default?
+ @ColumnInfo(name = "backoff_policy")
+ @Work.BackoffPolicy
+ int mBackoffPolicy = Work.BACKOFF_POLICY_EXPONENTIAL;
+
+ @ColumnInfo(name = "backoff_delay_duration")
+ long mBackoffDelayDuration = Work.DEFAULT_BACKOFF_DELAY_DURATION;
+
+ WorkSpec(String id) {
+ mId = id;
+ }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecConverter.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecConverter.java
new file mode 100644
index 0000000..2ed1b0c
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecConverter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+/**
+ * Converts a {@link WorkSpec} into type T.
+ * @param <T> The type to convert to.
+ */
+interface WorkSpecConverter<T> {
+ /**
+ * Converts a {@link WorkSpec} into type T.
+ * @param workSpec The {@link WorkSpec} to convert to type T.
+ * @return The converted {@link WorkSpec} as type T.
+ */
+ T convert(WorkSpec workSpec);
+
+ /**
+ * Converts a {@link Constraints.NetworkType} into an appropriate int mapping.
+ * @param networkType The {@link Constraints.NetworkType} to convert to an int.
+ * @return The converted {@link Constraints.NetworkType} as an int.
+ */
+ int convertNetworkType(@Constraints.NetworkType int networkType);
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecDao.java b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecDao.java
new file mode 100644
index 0000000..ab4ec17
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/WorkSpecDao.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static android.arch.persistence.room.OnConflictStrategy.FAIL;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+
+/**
+ * The Data Access Object for {@link WorkSpec}s.
+ */
+@Dao
+public interface WorkSpecDao {
+
+ /**
+ * Attempts to insert a {@link WorkSpec} into the database.
+ *
+ * @param workSpec The WorkSpec to insert.
+ */
+ @Insert(onConflict = FAIL)
+ void insertWorkSpec(WorkSpec workSpec);
+
+ /**
+ * @param id The identifier
+ * @return The WorkSpec associated with that id
+ */
+ @Query("SELECT * FROM workspec WHERE id=:id")
+ WorkSpec getWorkSpec(String id);
+
+ /**
+ * Updates the status of a {@link WorkSpec}.
+ *
+ * @param id The identifier for the {@link WorkSpec}
+ * @param status The new status
+ * @return The number of rows that were updated (should be 0 or 1)
+ */
+ @Query("UPDATE workspec SET status=:status WHERE id=:id")
+ int setWorkSpecStatus(String id, int status);
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/Worker.java b/background/workmanager/src/main/java/android/arch/background/workmanager/Worker.java
new file mode 100644
index 0000000..eca0477
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/Worker.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static android.arch.background.workmanager.Work.STATUS_ENQUEUED;
+import static android.arch.background.workmanager.Work.STATUS_FAILED;
+import static android.arch.background.workmanager.Work.STATUS_RUNNING;
+import static android.arch.background.workmanager.Work.STATUS_SUCCEEDED;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.concurrent.Callable;
+
+/**
+ * The basic unit of work.
+ *
+ * @param <T> The payload type for this unit of work.
+ */
+public abstract class Worker<T> implements Callable<T> {
+
+ private static final String TAG = "Worker";
+
+ protected Context mAppContext;
+ private WorkDatabase mWorkDatabase;
+ private WorkSpec mWorkSpec;
+
+ public Worker(Context appContext, WorkDatabase workDatabase, WorkSpec workSpec) {
+ this.mAppContext = appContext;
+ this.mWorkDatabase = workDatabase;
+ this.mWorkSpec = workSpec;
+ }
+
+ /**
+ * Override this method to do your actual background processing.
+ *
+ * @return The result payload
+ */
+ public abstract T doWork();
+
+ @Override
+ public final T call() {
+ String id = mWorkSpec.mId;
+ Log.v(TAG, "Worker.call for " + id);
+ WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
+ mWorkSpec.mStatus = STATUS_RUNNING;
+ workSpecDao.setWorkSpecStatus(id, STATUS_RUNNING);
+
+ T result = null;
+
+ try {
+ checkForInterruption();
+ result = doWork();
+ checkForInterruption();
+
+ Log.d(TAG, "Work succeeded for " + id);
+ mWorkSpec.mStatus = STATUS_SUCCEEDED;
+ workSpecDao.setWorkSpecStatus(id, STATUS_SUCCEEDED);
+ } catch (Exception e) {
+ // TODO: Retry policies.
+ if (e instanceof InterruptedException) {
+ Log.d(TAG, "Work interrupted for " + id);
+ mWorkSpec.mStatus = STATUS_ENQUEUED;
+ workSpecDao.setWorkSpecStatus(id, STATUS_ENQUEUED);
+ } else {
+ Log.d(TAG, "Work failed for " + id, e);
+ mWorkSpec.mStatus = STATUS_FAILED;
+ workSpecDao.setWorkSpecStatus(id, STATUS_FAILED);
+ }
+ }
+
+ return result;
+ }
+
+ static Worker fromWorkSpec(
+ final Context context,
+ final WorkDatabase workDatabase,
+ final WorkSpec workSpec) {
+ Context appContext = context.getApplicationContext();
+ String workerClassName = workSpec.mWorkerClassName;
+ try {
+ Class<?> clazz = Class.forName(workerClassName);
+ if (Worker.class.isAssignableFrom(clazz)) {
+ return (Worker) clazz
+ .getConstructor(Context.class, WorkDatabase.class, WorkSpec.class)
+ .newInstance(appContext, workDatabase, workSpec);
+ } else {
+ Log.e(TAG, "" + workerClassName + " is not of type Worker");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Trouble instantiating " + workerClassName, e);
+ }
+ return null;
+ }
+
+ private void checkForInterruption() throws InterruptedException {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ }
+}
diff --git a/background/workmanager/src/test/java/android/arch/background/workmanager/ArgumentsTest.java b/background/workmanager/src/test/java/android/arch/background/workmanager/ArgumentsTest.java
new file mode 100644
index 0000000..4e8823e
--- /dev/null
+++ b/background/workmanager/src/test/java/android/arch/background/workmanager/ArgumentsTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+@SmallTest
+public class ArgumentsTest {
+ private static final String KEY1 = "key1";
+ private static final String KEY2 = "key2";
+
+ @Test
+ public void empty() throws IOException, ClassNotFoundException {
+ Arguments args = new Arguments();
+
+ byte[] byteArray = Arguments.toByteArray(args);
+ Arguments restoredArgs = Arguments.fromByteArray(byteArray);
+
+ assertNotNull(restoredArgs);
+ assertEquals(0, restoredArgs.size());
+ }
+
+ @Test
+ public void serializeString() throws IOException, ClassNotFoundException {
+ Arguments args = new Arguments();
+ String expectedValue1 = "value1";
+ String expectedValue2 = "value2";
+ args.putString(KEY1, expectedValue1);
+ args.putString(KEY2, expectedValue2);
+
+ byte[] byteArray = Arguments.toByteArray(args);
+ Arguments restoredArgs = Arguments.fromByteArray(byteArray);
+
+ assertNotNull(restoredArgs);
+ assertEquals(2, restoredArgs.size());
+
+ String actualValue1 = restoredArgs.getString(KEY1, null);
+ assertNotNull(actualValue1);
+ assertEquals(expectedValue1, actualValue1);
+
+ String actualValue2 = restoredArgs.getString(KEY2, null);
+ assertNotNull(actualValue2);
+ assertEquals(expectedValue2, actualValue2);
+ }
+
+ @Test
+ public void serializeIntArray() throws IOException, ClassNotFoundException {
+ Arguments args = new Arguments();
+ int[] expectedValue1 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ int[] expectedValue2 = new int[]{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+ args.putIntArray(KEY1, expectedValue1);
+ args.putIntArray(KEY2, expectedValue2);
+
+ byte[] byteArray = Arguments.toByteArray(args);
+ Arguments restoredArgs = Arguments.fromByteArray(byteArray);
+
+ assertNotNull(restoredArgs);
+ assertEquals(2, restoredArgs.size());
+
+ int[] actualValue1 = restoredArgs.getIntArray(KEY1);
+ assertArrayEquals(expectedValue1, actualValue1);
+
+ int[] actualValue2 = restoredArgs.getIntArray(KEY2);
+ assertArrayEquals(expectedValue2, actualValue2);
+ }
+}
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index c994f6e..f1a2d79 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -69,4 +69,9 @@
* Version code for shared testing code of flatfoot
*/
public static final Version ARCH_CORE_TESTING = FLATFOOT_1_0_BATCH;
+
+ /**
+ * Version code for Background WorkManager
+ */
+ public static final Version WORKMANAGER = new Version("1.0.0-alpha1");
}
diff --git a/v17/leanback/res/values-bn/strings.xml b/v17/leanback/res/values-bn/strings.xml
index d0e08c4..899c564 100644
--- a/v17/leanback/res/values-bn/strings.xml
+++ b/v17/leanback/res/values-bn/strings.xml
@@ -21,7 +21,7 @@
<string name="orb_search_action" msgid="5651268540267663887">"অনুসন্ধান অ্যাকশন"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"অনুসন্ধান"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"অনুসন্ধান করতে বলুন"</string>
- <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> খুঁজুন"</string>
+ <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করুন"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করতে বলুন"</string>
<string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
@@ -33,11 +33,11 @@
<string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"পেছনের দিকে যান %1$dX"</string>
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"সরাসরি পরেরটিতে চলে যান"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"সরাসরি আগেরটিতে চলে যান"</string>
- <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"আরও অ্যাকশন"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"আরো অ্যাকশন"</string>
<string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"উপরের দিকে বুড়ো আঙ্গুল নির্দেশিত চিহ্ন নির্বাচন মুক্ত করুন"</string>
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"উপরের দিকে বুড়ো আঙ্গুল নির্দেশিত চিহ্ন নির্বাচিত করুন"</string>
<string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"নীচের দিকে বুড়ো আঙ্গুল নির্দেশিত চিহ্ন নির্বাচন মুক্ত করুন"</string>
- <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"নীচের দিকে বুড়ো আঙ্গুল নির্দেশিত চিহ্ন বেছে নিন"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"নীচের দিকে বুড়ো আঙ্গুল নির্দেশিত চিহ্ন নির্বাচন করুন"</string>
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"একটিরও পুনরাবৃত্তি করবেন না"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"সবগুলির পুনরাবৃত্তি করুন"</string>
<string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"একটির পুনরাবৃত্তি করুন"</string>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 00ef8ab..6287f14 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -41,11 +41,11 @@
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"कुछ भी न दोहराएं"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"सभी को दोहराएं"</string>
<string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"एक दोहराएं"</string>
- <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"शफ़ल करना चालू करें"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"फेर-बदल सक्षम करें"</string>
<string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"फेर-बदल अक्षम करें"</string>
- <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"अच्छी क्वालिटी में चलाएं"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"उच्च गुणवत्ता सक्षम करें"</string>
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करें"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"सबटाइटल चालू करें"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षक सक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"उपशीर्षक अक्षम करें"</string>
<string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोड में चित्र डालें"</string>
<string name="lb_playback_time_separator" msgid="3208380806582304911">"/"</string>
@@ -55,5 +55,5 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी रखें"</string>
<string name="lb_media_player_error" msgid="3650250994187305396">"मीडिया प्लेयर गड़बड़ी कोड %1$d कुछ और %2$d"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"शुरू करें"</string>
- <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"आगे बढ़ें"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"अगला"</string>
</resources>
diff --git a/v17/leanback/res/values-pa/strings.xml b/v17/leanback/res/values-pa/strings.xml
index 57956ee..4b3c515 100644
--- a/v17/leanback/res/values-pa/strings.xml
+++ b/v17/leanback/res/values-pa/strings.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ ਮੀਨੂ"</string>
- <string name="orb_search_action" msgid="5651268540267663887">"ਖੋਜ ਕਾਰਵਾਈ"</string>
+ <string name="orb_search_action" msgid="5651268540267663887">"ਖੋਜ ਕਿਰਿਆ"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ਖੋਜੋ"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ਖੋਜਣ ਲਈ ਬੋਲੋ"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ਖੋਜੋ"</string>
@@ -27,12 +27,12 @@
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"ਪਲੇ ਕਰੋ"</string>
<string name="lb_playback_controls_pause" msgid="6189521112079849518">"ਰੋਕੋ"</string>
- <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ਤੇਜ਼ੀ ਨਾਲ ਅੱਗੇ ਭੇਜੋ"</string>
- <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX ਨੂੰ ਤੇਜ਼ੀ ਨਾਲ ਅੱਗੇ ਭੇਜੋ"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ਅੱਗੇ ਭੇਜੋ"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string>
<string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ਰੀਵਾਈਂਡ"</string>
<string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX ਨੂੰ ਰੀਵਾਈਂਡ ਕਰੋ"</string>
- <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ਅਗਲੇ ਨੂੰ ਛੱਡੋ"</string>
- <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ਪਿਛਲੇ ਨੂੰ ਛੱਡੋ"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ਅਗਲਾ ਨੂੰ ਛੱਡੋ"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ਪਿਛਲਾ ਨੂੰ ਛੱਡੋ"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ਹੋਰ ਕਿਰਿਆਵਾਂ"</string>
<string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"ਥੰਬ ਅਪ ਨੂੰ ਅਚੋਣਵਾਂ ਕਰੋ"</string>
<string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"ਥੰਬ ਅਪ ਨੂੰ ਚੁਣੋ"</string>
diff --git a/v7/appcompat/res/values-bn/strings.xml b/v7/appcompat/res/values-bn/strings.xml
index 2ea7591..5959799 100644
--- a/v7/appcompat/res/values-bn/strings.xml
+++ b/v7/appcompat/res/values-bn/strings.xml
@@ -19,11 +19,11 @@
<string name="abc_action_mode_done" msgid="4076576682505996667">"সম্পন্ন হয়েছে"</string>
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"হোম এ নেভিগেট করুন"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"উপরের দিকে নেভিগেট করুন"</string>
- <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"আরও বিকল্প"</string>
+ <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"আরো বিকল্প"</string>
<string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"সঙ্কুচিত করুন"</string>
- <string name="abc_searchview_description_search" msgid="8264924765203268293">"খুঁজুন"</string>
+ <string name="abc_searchview_description_search" msgid="8264924765203268293">"অনুসন্ধান করুন"</string>
<string name="abc_search_hint" msgid="7723749260725869598">"অনুসন্ধান..."</string>
- <string name="abc_searchview_description_query" msgid="2550479030709304392">"ক্যোয়ারী খুঁজুন"</string>
+ <string name="abc_searchview_description_query" msgid="2550479030709304392">"ক্যোয়ারী অনুসন্ধান করুন"</string>
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"ক্যোয়ারী সাফ করুন"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ক্যোয়ারী জমা দিন"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ভয়েস অনুসন্ধান"</string>
@@ -33,5 +33,5 @@
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে শেয়ার করুন"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"চালু"</string>
<string name="abc_capital_off" msgid="121134116657445385">"বন্ধ"</string>
- <string name="search_menu_title" msgid="146198913615257606">"খুঁজুন"</string>
+ <string name="search_menu_title" msgid="146198913615257606">"অনুসন্ধান করুন"</string>
</resources>
diff --git a/v7/appcompat/res/values-hi/strings.xml b/v7/appcompat/res/values-hi/strings.xml
index 3a393c7..0d90e55 100644
--- a/v7/appcompat/res/values-hi/strings.xml
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -16,11 +16,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="abc_action_mode_done" msgid="4076576682505996667">"हो गया"</string>
+ <string name="abc_action_mode_done" msgid="4076576682505996667">"पूर्ण"</string>
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"होम पेज पर जाएं"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"ऊपर जाएं"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ज़्यादा विकल्प"</string>
- <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"छोटा करें"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त करें"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"सर्च करें"</string>
<string name="abc_search_hint" msgid="7723749260725869598">"खोजा जा रहा है…"</string>
<string name="abc_searchview_description_query" msgid="2550479030709304392">"सर्च क्वेरी"</string>
diff --git a/v7/appcompat/res/values-pa/strings.xml b/v7/appcompat/res/values-pa/strings.xml
index 7f28ac8..bc2e6ea 100644
--- a/v7/appcompat/res/values-pa/strings.xml
+++ b/v7/appcompat/res/values-pa/strings.xml
@@ -17,13 +17,13 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="abc_action_mode_done" msgid="4076576682505996667">"ਹੋ ਗਿਆ"</string>
- <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ਹੋਮ \'ਤੇ ਜਾਓ"</string>
- <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ਉੱਪਰ ਜਾਓ"</string>
+ <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ਹੋਮ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
+ <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ਉੱਪਰ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ਹੋਰ ਚੋਣਾਂ"</string>
<string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ਨਸ਼ਟ ਕਰੋ"</string>
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ਖੋਜੋ"</string>
<string name="abc_search_hint" msgid="7723749260725869598">"ਖੋਜ…"</string>
- <string name="abc_searchview_description_query" msgid="2550479030709304392">"ਖੋਜ ਪੁੱਛਗਿੱਛ"</string>
+ <string name="abc_searchview_description_query" msgid="2550479030709304392">"ਸਵਾਲ ਖੋਜੋ"</string>
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"ਸਵਾਲ ਹਟਾਓ"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ਸਵਾਲ ਪ੍ਰਸਤੁਤ ਕਰੋ"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ਵੌਇਸ ਖੋਜ"</string>
@@ -33,5 +33,5 @@
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ਇਸ ਨਾਲ ਸਾਂਝਾ ਕਰੋ"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"ਤੇ"</string>
<string name="abc_capital_off" msgid="121134116657445385">"ਬੰਦ"</string>
- <string name="search_menu_title" msgid="146198913615257606">"ਖੋਜੋ"</string>
+ <string name="search_menu_title" msgid="146198913615257606">"ਖੋਜ"</string>
</resources>
diff --git a/v7/appcompat/res/values-ta/strings.xml b/v7/appcompat/res/values-ta/strings.xml
index 4a2ad2f..7daeaaf 100644
--- a/v7/appcompat/res/values-ta/strings.xml
+++ b/v7/appcompat/res/values-ta/strings.xml
@@ -31,7 +31,7 @@
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"எல்லாம் காட்டு"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="3300176832234831527">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> மூலம் பகிர்"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"இதனுடன் பகிர்"</string>
- <string name="abc_capital_on" msgid="3405795526292276155">"ஆன்"</string>
- <string name="abc_capital_off" msgid="121134116657445385">"ஆஃப்"</string>
+ <string name="abc_capital_on" msgid="3405795526292276155">"இயக்கு"</string>
+ <string name="abc_capital_off" msgid="121134116657445385">"முடக்கு"</string>
<string name="search_menu_title" msgid="146198913615257606">"தேடு"</string>
</resources>
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
index f9ac43b..adf3e88 100644
--- a/v7/mediarouter/res/values-hi/strings.xml
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -30,8 +30,8 @@
<string name="mr_controller_play" msgid="683634565969987458">"चलाएं"</string>
<string name="mr_controller_pause" msgid="5451884435510905406">"रोकें"</string>
<string name="mr_controller_stop" msgid="735874641921425123">"बंद करें"</string>
- <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार करें"</string>
- <string name="mr_controller_collapse_group" msgid="7924809056904240926">"छोटा करें"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तृत करें"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"संक्षिप्त करें"</string>
<string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
<string name="mr_controller_volume_slider" msgid="2361785992211841709">"वॉल्यूम स्लाइडर"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कोई मीडिया चयनित नहीं है"</string>
diff --git a/v7/mediarouter/res/values-ta/strings.xml b/v7/mediarouter/res/values-ta/strings.xml
index 99c6172..59dac88 100644
--- a/v7/mediarouter/res/values-ta/strings.xml
+++ b/v7/mediarouter/res/values-ta/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mr_system_route_name" msgid="5441529851481176817">"சிஸ்டம்"</string>
+ <string name="mr_system_route_name" msgid="5441529851481176817">"அமைப்பு"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"சாதனங்கள்"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"திரையிடு பட்டன்"</string>
<string name="mr_cast_button_disconnected" msgid="816305490427819240">"அனுப்புதல் பொத்தான். துண்டிக்கப்பட்டது"</string>