Merge "Fix bugs in user restriction migration" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 6110d1c..1ebec4e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8006,6 +8006,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8198,6 +8199,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -37632,6 +37634,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/api/system-current.txt b/api/system-current.txt
index c01f1c3..55df364 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8297,6 +8297,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8499,6 +8500,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -40380,6 +40382,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/api/test-current.txt b/api/test-current.txt
index 96d29d1..8fcb9bd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8009,6 +8009,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8202,6 +8203,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -37647,6 +37649,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 3385a17..980329f 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -807,8 +807,6 @@
     }
 
     /**
-     * AnimatorSet is only reversible when the set contains no sequential animation, and no child
-     * animators have a start delay.
      * @hide
      */
     @Override
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5e8d190..8884949 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -460,7 +460,7 @@
     private File getPreferencesDir() {
         synchronized (mSync) {
             if (mPreferencesDir == null) {
-                mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
+                mPreferencesDir = new File(getDataDir(), "shared_prefs");
             }
             return ensurePrivateDirExists(mPreferencesDir);
         }
@@ -525,7 +525,7 @@
     public File getFilesDir() {
         synchronized (mSync) {
             if (mFilesDir == null) {
-                mFilesDir = new File(getDataDirFile(), "files");
+                mFilesDir = new File(getDataDir(), "files");
             }
             return ensurePrivateDirExists(mFilesDir);
         }
@@ -535,7 +535,7 @@
     public File getNoBackupFilesDir() {
         synchronized (mSync) {
             if (mNoBackupFilesDir == null) {
-                mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
+                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
             }
             return ensurePrivateDirExists(mNoBackupFilesDir);
         }
@@ -587,7 +587,7 @@
     public File getCacheDir() {
         synchronized (mSync) {
             if (mCacheDir == null) {
-                mCacheDir = new File(getDataDirFile(), "cache");
+                mCacheDir = new File(getDataDir(), "cache");
             }
             return ensurePrivateDirExists(mCacheDir);
         }
@@ -597,7 +597,7 @@
     public File getCodeCacheDir() {
         synchronized (mSync) {
             if (mCodeCacheDir == null) {
-                mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+                mCodeCacheDir = new File(getDataDir(), "code_cache");
             }
             return ensurePrivateDirExists(mCodeCacheDir);
         }
@@ -724,7 +724,7 @@
                 if ("android".equals(getPackageName())) {
                     mDatabasesDir = new File("/data/system");
                 } else {
-                    mDatabasesDir = new File(getDataDirFile(), "databases");
+                    mDatabasesDir = new File(getDataDir(), "databases");
                 }
             }
             return ensurePrivateDirExists(mDatabasesDir);
@@ -1920,7 +1920,8 @@
         return mDisplayAdjustments;
     }
 
-    private File getDataDirFile() {
+    @Override
+    public File getDataDir() {
         if (mPackageInfo != null) {
             File res = null;
             if (isCredentialEncryptedStorage()) {
@@ -1947,7 +1948,7 @@
     public File getDir(String name, int mode) {
         checkMode(mode);
         name = "app_" + name;
-        File file = makeFilename(getDataDirFile(), name);
+        File file = makeFilename(getDataDir(), name);
         if (!file.exists()) {
             file.mkdir();
             setFilePermissionsFromMode(file.getPath(), mode,
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 63f1425..aeb3156 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -308,14 +308,31 @@
         final String packageName = getPackageName();
         final ApplicationInfo appInfo = getApplicationInfo();
 
-        String rootDir = new File(appInfo.dataDir).getCanonicalPath();
-        String filesDir = getFilesDir().getCanonicalPath();
-        String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
-        String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
-        String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
-        String cacheDir = getCacheDir().getCanonicalPath();
-        String codeCacheDir = getCodeCacheDir().getCanonicalPath();
-        String libDir = (appInfo.nativeLibraryDir != null)
+        // System apps have control over where their default storage context
+        // is pointed, so we're always explicit when building paths.
+        final Context ceContext = createCredentialEncryptedStorageContext();
+        final String rootDir = ceContext.getDataDir().getCanonicalPath();
+        final String filesDir = ceContext.getFilesDir().getCanonicalPath();
+        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+        final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
+                .getCanonicalPath();
+        final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
+                .getCanonicalPath();
+        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
+        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+        final Context deContext = createDeviceEncryptedStorageContext();
+        final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
+        final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+        final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
+                .getCanonicalPath();
+        final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
+                .getParentFile().getCanonicalPath();
+        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
+        final String libDir = (appInfo.nativeLibraryDir != null)
                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
                 : null;
 
@@ -325,30 +342,48 @@
         final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
 
         // Add the directories we always exclude.
+        traversalExcludeSet.add(filesDir);
+        traversalExcludeSet.add(noBackupDir);
+        traversalExcludeSet.add(databaseDir);
+        traversalExcludeSet.add(sharedPrefsDir);
         traversalExcludeSet.add(cacheDir);
         traversalExcludeSet.add(codeCacheDir);
-        traversalExcludeSet.add(nobackupDir);
+
+        traversalExcludeSet.add(deviceFilesDir);
+        traversalExcludeSet.add(deviceNoBackupDir);
+        traversalExcludeSet.add(deviceDatabaseDir);
+        traversalExcludeSet.add(deviceSharedPrefsDir);
+        traversalExcludeSet.add(deviceCacheDir);
+        traversalExcludeSet.add(deviceCodeCacheDir);
+
         if (libDir != null) {
             traversalExcludeSet.add(libDir);
         }
 
-        traversalExcludeSet.add(databaseDir);
-        traversalExcludeSet.add(sharedPrefsDir);
-        traversalExcludeSet.add(filesDir);
-
         // Root dir first.
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(rootDir);
 
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceRootDir);
+
         // Data dir next.
         traversalExcludeSet.remove(filesDir);
         applyXmlFiltersAndDoFullBackupForDomain(
-                packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
+                packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(filesDir);
 
+        traversalExcludeSet.remove(deviceFilesDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceFilesDir);
+
         // Database directory.
         traversalExcludeSet.remove(databaseDir);
         applyXmlFiltersAndDoFullBackupForDomain(
@@ -356,6 +391,12 @@
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(databaseDir);
 
+        traversalExcludeSet.remove(deviceDatabaseDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceDatabaseDir);
+
         // SharedPrefs.
         traversalExcludeSet.remove(sharedPrefsDir);
         applyXmlFiltersAndDoFullBackupForDomain(
@@ -363,6 +404,12 @@
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(sharedPrefsDir);
 
+        traversalExcludeSet.remove(deviceSharedPrefsDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceSharedPrefsDir);
+
         // getExternalFilesDir() location associated with this app.  Technically there should
         // not be any files here if the app does not properly have permission to access
         // external storage, but edge cases happen. fullBackupFileTree() catches
@@ -445,27 +492,49 @@
      */
     public final void fullBackupFile(File file, FullBackupDataOutput output) {
         // Look up where all of our various well-defined dir trees live on this device
-        String mainDir;
-        String filesDir;
-        String nbFilesDir;
-        String dbDir;
-        String spDir;
-        String cacheDir;
-        String codeCacheDir;
-        String libDir;
+        final String rootDir;
+        final String filesDir;
+        final String nbFilesDir;
+        final String dbDir;
+        final String spDir;
+        final String cacheDir;
+        final String codeCacheDir;
+        final String deviceRootDir;
+        final String deviceFilesDir;
+        final String deviceNbFilesDir;
+        final String deviceDbDir;
+        final String deviceSpDir;
+        final String deviceCacheDir;
+        final String deviceCodeCacheDir;
+        final String libDir;
+
         String efDir = null;
         String filePath;
 
         ApplicationInfo appInfo = getApplicationInfo();
 
         try {
-            mainDir = new File(appInfo.dataDir).getCanonicalPath();
-            filesDir = getFilesDir().getCanonicalPath();
-            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
-            dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
-            spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
-            cacheDir = getCacheDir().getCanonicalPath();
-            codeCacheDir = getCodeCacheDir().getCanonicalPath();
+            // System apps have control over where their default storage context
+            // is pointed, so we're always explicit when building paths.
+            final Context ceContext = createCredentialEncryptedStorageContext();
+            rootDir = ceContext.getDataDir().getCanonicalPath();
+            filesDir = ceContext.getFilesDir().getCanonicalPath();
+            nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+            dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+            spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
+            cacheDir = ceContext.getCacheDir().getCanonicalPath();
+            codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+            final Context deContext = createDeviceEncryptedStorageContext();
+            deviceRootDir = deContext.getDataDir().getCanonicalPath();
+            deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+            deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+            deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+            deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
+                    .getCanonicalPath();
+            deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+            deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
             libDir = (appInfo.nativeLibraryDir == null)
                     ? null
                     : new File(appInfo.nativeLibraryDir).getCanonicalPath();
@@ -489,8 +558,11 @@
 
         if (filePath.startsWith(cacheDir)
                 || filePath.startsWith(codeCacheDir)
-                || filePath.startsWith(libDir)
-                || filePath.startsWith(nbFilesDir)) {
+                || filePath.startsWith(nbFilesDir)
+                || filePath.startsWith(deviceCacheDir)
+                || filePath.startsWith(deviceCodeCacheDir)
+                || filePath.startsWith(deviceNbFilesDir)
+                || filePath.startsWith(libDir)) {
             Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
             return;
         }
@@ -504,11 +576,23 @@
             domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
             rootpath = spDir;
         } else if (filePath.startsWith(filesDir)) {
-            domain = FullBackup.DATA_TREE_TOKEN;
+            domain = FullBackup.FILES_TREE_TOKEN;
             rootpath = filesDir;
-        } else if (filePath.startsWith(mainDir)) {
+        } else if (filePath.startsWith(rootDir)) {
             domain = FullBackup.ROOT_TREE_TOKEN;
-            rootpath = mainDir;
+            rootpath = rootDir;
+        } else if (filePath.startsWith(deviceDbDir)) {
+            domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+            rootpath = deviceDbDir;
+        } else if (filePath.startsWith(deviceSpDir)) {
+            domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
+            rootpath = deviceSpDir;
+        } else if (filePath.startsWith(deviceFilesDir)) {
+            domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
+            rootpath = deviceFilesDir;
+        } else if (filePath.startsWith(deviceRootDir)) {
+            domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
+            rootpath = deviceRootDir;
         } else if ((efDir != null) && filePath.startsWith(efDir)) {
             domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
             rootpath = efDir;
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9ea2ba2..cdc80e3 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -55,13 +55,22 @@
 
     public static final String APK_TREE_TOKEN = "a";
     public static final String OBB_TREE_TOKEN = "obb";
+
     public static final String ROOT_TREE_TOKEN = "r";
-    public static final String DATA_TREE_TOKEN = "f";
+    public static final String FILES_TREE_TOKEN = "f";
     public static final String NO_BACKUP_TREE_TOKEN = "nb";
     public static final String DATABASE_TREE_TOKEN = "db";
     public static final String SHAREDPREFS_TREE_TOKEN = "sp";
-    public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
     public static final String CACHE_TREE_TOKEN = "c";
+
+    public static final String DEVICE_ROOT_TREE_TOKEN = "d_r";
+    public static final String DEVICE_FILES_TREE_TOKEN = "d_f";
+    public static final String DEVICE_NO_BACKUP_TREE_TOKEN = "d_nb";
+    public static final String DEVICE_DATABASE_TREE_TOKEN = "d_db";
+    public static final String DEVICE_SHAREDPREFS_TREE_TOKEN = "d_sp";
+    public static final String DEVICE_CACHE_TREE_TOKEN = "d_c";
+
+    public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
     public static final String SHARED_STORAGE_TOKEN = "shared";
 
     public static final String APPS_PREFIX = "apps/";
@@ -201,10 +210,18 @@
         private final File DATABASE_DIR;
         private final File ROOT_DIR;
         private final File SHAREDPREF_DIR;
-        private final File EXTERNAL_DIR;
         private final File CACHE_DIR;
         private final File NOBACKUP_DIR;
 
+        private final File DEVICE_FILES_DIR;
+        private final File DEVICE_DATABASE_DIR;
+        private final File DEVICE_ROOT_DIR;
+        private final File DEVICE_SHAREDPREF_DIR;
+        private final File DEVICE_CACHE_DIR;
+        private final File DEVICE_NOBACKUP_DIR;
+
+        private final File EXTERNAL_DIR;
+
         final int mFullBackupContent;
         final PackageManager mPackageManager;
         final String mPackageName;
@@ -214,7 +231,7 @@
          */
         String tokenToDirectoryPath(String domainToken) {
             try {
-                if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) {
+                if (domainToken.equals(FullBackup.FILES_TREE_TOKEN)) {
                     return FILES_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
                     return DATABASE_DIR.getCanonicalPath();
@@ -224,14 +241,26 @@
                     return SHAREDPREF_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
                     return CACHE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+                    return NOBACKUP_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_FILES_TREE_TOKEN)) {
+                    return DEVICE_FILES_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_DATABASE_TREE_TOKEN)) {
+                    return DEVICE_DATABASE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_ROOT_TREE_TOKEN)) {
+                    return DEVICE_ROOT_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN)) {
+                    return DEVICE_SHAREDPREF_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_CACHE_TREE_TOKEN)) {
+                    return DEVICE_CACHE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_NO_BACKUP_TREE_TOKEN)) {
+                    return DEVICE_NOBACKUP_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
                     if (EXTERNAL_DIR != null) {
                         return EXTERNAL_DIR.getCanonicalPath();
                     } else {
                         return null;
                     }
-                } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
-                    return NOBACKUP_DIR.getCanonicalPath();
                 }
                 // Not a supported location
                 Log.i(TAG, "Unrecognized domain " + domainToken);
@@ -257,12 +286,25 @@
             mFullBackupContent = context.getApplicationInfo().fullBackupContent;
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
-            FILES_DIR = context.getFilesDir();
-            DATABASE_DIR = context.getDatabasePath("foo").getParentFile();
-            ROOT_DIR = new File(context.getApplicationInfo().dataDir);
-            SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile();
-            CACHE_DIR = context.getCacheDir();
-            NOBACKUP_DIR = context.getNoBackupFilesDir();
+
+            // System apps have control over where their default storage context
+            // is pointed, so we're always explicit when building paths.
+            final Context ceContext = context.createCredentialEncryptedStorageContext();
+            FILES_DIR = ceContext.getFilesDir();
+            DATABASE_DIR = ceContext.getDatabasePath("foo").getParentFile();
+            ROOT_DIR = ceContext.getDataDir();
+            SHAREDPREF_DIR = ceContext.getSharedPreferencesPath("foo").getParentFile();
+            CACHE_DIR = ceContext.getCacheDir();
+            NOBACKUP_DIR = ceContext.getNoBackupFilesDir();
+
+            final Context deContext = context.createDeviceEncryptedStorageContext();
+            DEVICE_FILES_DIR = deContext.getFilesDir();
+            DEVICE_DATABASE_DIR = deContext.getDatabasePath("foo").getParentFile();
+            DEVICE_ROOT_DIR = deContext.getDataDir();
+            DEVICE_SHAREDPREF_DIR = deContext.getSharedPreferencesPath("foo").getParentFile();
+            DEVICE_CACHE_DIR = deContext.getCacheDir();
+            DEVICE_NOBACKUP_DIR = deContext.getNoBackupFilesDir();
+
             if (android.os.Process.myUid() != Process.SYSTEM_UID) {
                 EXTERNAL_DIR = context.getExternalFilesDir(null);
             } else {
@@ -403,6 +445,13 @@
                                 Log.v(TAG_XML_PARSER, "...automatically generated "
                                         + canonicalJournalPath + ". Ignore if nonexistent.");
                             }
+                            final String canonicalWalPath =
+                                    canonicalFile.getCanonicalPath() + "-wal";
+                            activeSet.add(canonicalWalPath);
+                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+                                Log.v(TAG_XML_PARSER, "...automatically generated "
+                                        + canonicalWalPath + ". Ignore if nonexistent.");
+                            }
                         }
 
                         // Special case for sharedpref files (not dirs) also add ".xml" suffix file.
@@ -485,11 +534,19 @@
             if ("root".equals(xmlDomain)) {
                 return FullBackup.ROOT_TREE_TOKEN;
             } else if ("file".equals(xmlDomain)) {
-                return FullBackup.DATA_TREE_TOKEN;
+                return FullBackup.FILES_TREE_TOKEN;
             } else if ("database".equals(xmlDomain)) {
                 return FullBackup.DATABASE_TREE_TOKEN;
             } else if ("sharedpref".equals(xmlDomain)) {
                 return FullBackup.SHAREDPREFS_TREE_TOKEN;
+            } else if ("device_root".equals(xmlDomain)) {
+                return FullBackup.DEVICE_ROOT_TREE_TOKEN;
+            } else if ("device_file".equals(xmlDomain)) {
+                return FullBackup.DEVICE_FILES_TREE_TOKEN;
+            } else if ("device_database".equals(xmlDomain)) {
+                return FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+            } else if ("device_sharedpref".equals(xmlDomain)) {
+                return FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
             } else if ("external".equals(xmlDomain)) {
                 return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
             } else {
@@ -542,6 +599,14 @@
                 return ROOT_DIR;
             } else if ("sharedpref".equals(domain)) {
                 return SHAREDPREF_DIR;
+            } else if ("device_file".equals(domain)) {
+                return DEVICE_FILES_DIR;
+            } else if ("device_database".equals(domain)) {
+                return DEVICE_DATABASE_DIR;
+            } else if ("device_root".equals(domain)) {
+                return DEVICE_ROOT_DIR;
+            } else if ("device_sharedpref".equals(domain)) {
+                return DEVICE_SHAREDPREF_DIR;
             } else if ("external".equals(domain)) {
                 return EXTERNAL_DIR;
             } else {
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 13aeef0..2e3aca4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -25,9 +25,15 @@
 import android.net.DataUsageRequest;
 import android.net.NetworkIdentity;
 import android.net.NetworkTemplate;
+import android.net.INetworkStatsService;
+import android.os.Binder;
 import android.os.Build;
+import android.os.Message;
+import android.os.Messenger;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
 /**
@@ -75,16 +81,26 @@
  * not included.
  */
 public class NetworkStatsManager {
-    private final static String TAG = "NetworkStatsManager";
+    private static final String TAG = "NetworkStatsManager";
+    private static final boolean DBG = false;
+
+    /** @hide */
+    public static final int CALLBACK_LIMIT_REACHED = 0;
+    /** @hide */
+    public static final int CALLBACK_RELEASED = 1;
 
     private final Context mContext;
+    private final INetworkStatsService mService;
 
     /**
      * {@hide}
      */
     public NetworkStatsManager(Context context) {
         mContext = context;
+        mService = INetworkStatsService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
     }
+
     /**
      * Query network usage statistics summaries. Result is summarised data usage for the whole
      * device. Result is a single Bucket aggregated over time, state, uid, tag and roaming. This
@@ -322,7 +338,40 @@
         checkNotNull(policy, "DataUsagePolicy cannot be null");
         checkNotNull(callback, "DataUsageCallback cannot be null");
 
-        // TODO: Implement stub.
+        final Looper looper;
+        if (handler == null) {
+            looper = Looper.myLooper();
+        } else {
+            looper = handler.getLooper();
+        }
+
+        if (DBG) Log.d(TAG, "registerDataUsageCallback called with " + policy);
+
+        NetworkTemplate[] templates;
+        if (policy.subscriberIds == null || policy.subscriberIds.length == 0) {
+            templates = new NetworkTemplate[1];
+            templates[0] = createTemplate(policy.networkType, null /* subscriberId */);
+        } else {
+            templates = new NetworkTemplate[policy.subscriberIds.length];
+            for (int i = 0; i < policy.subscriberIds.length; i++) {
+                templates[i] = createTemplate(policy.networkType, policy.subscriberIds[i]);
+            }
+        }
+        DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+                templates, policy.uids, policy.thresholdInBytes);
+        try {
+            CallbackHandler callbackHandler = new CallbackHandler(looper, callback);
+            callback.request = mService.registerDataUsageCallback(
+                    mContext.getOpPackageName(), request, new Messenger(callbackHandler),
+                    new Binder());
+            if (DBG) Log.d(TAG, "registerDataUsageCallback returned " + callback.request);
+
+            if (callback.request == null) {
+                Log.e(TAG, "Request from callback is null; should not happen");
+            }
+        } catch (RemoteException e) {
+            if (DBG) Log.d(TAG, "Remote exception when registering callback");
+        }
     }
 
     /**
@@ -331,9 +380,15 @@
      * @param callback The {@link DataUsageCallback} used when registering.
      */
     public void unregisterDataUsageCallback(DataUsageCallback callback) {
-        checkNotNull(callback, "DataUsageCallback cannot be null");
-
-        // TODO: Implement stub.
+        if (callback == null || callback.request == null
+                || callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
+            throw new IllegalArgumentException("Invalid DataUsageCallback");
+        }
+        try {
+            mService.unregisterDataUsageRequest(callback.request);
+        } catch (RemoteException e) {
+            if (DBG) Log.d(TAG, "Remote exception when unregistering callback");
+        }
     }
 
     /**
@@ -366,4 +421,38 @@
         }
         return template;
     }
+
+    private static class CallbackHandler extends Handler {
+        private DataUsageCallback mCallback;
+        CallbackHandler(Looper looper, DataUsageCallback callback) {
+            super(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            DataUsageRequest request =
+                    (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
+
+            switch (message.what) {
+                case CALLBACK_LIMIT_REACHED: {
+                    if (mCallback != null) {
+                        mCallback.onLimitReached();
+                    } else {
+                        Log.e(TAG, "limit reached with released callback for " + request);
+                    }
+                    break;
+                }
+                case CALLBACK_RELEASED: {
+                    if (DBG) Log.d(TAG, "callback released for " + request);
+                    mCallback = null;
+                    break;
+                }
+            }
+        }
+
+        private static Object getObject(Message msg, String key) {
+            return msg.getData().getParcelable(key);
+        }
+    }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fff0c14..0cdbef0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -811,6 +811,25 @@
     public abstract File getSharedPreferencesPath(String name);
 
     /**
+     * Returns the absolute path to the directory on the filesystem where all
+     * private files belonging to this app are stored. This is the top-level
+     * directory under which {@link #getFilesDir()}, {@link #getCacheDir()}, etc
+     * are contained. Apps should <em>not</em> create any files or directories
+     * as direct children of this directory, since it's a reserved namespace
+     * belonging to the platform. Instead, use {@link #getDir(String, int)} or
+     * other storage APIs.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
+     *
+     * @see #getDir(String, int)
+     */
+    public abstract File getDataDir();
+
+    /**
      * Returns the absolute path to the directory on the filesystem where files
      * created with {@link #openFileOutput} are stored.
      * <p>
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 61b87a9..323c9bf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -212,6 +212,11 @@
     }
 
     @Override
+    public File getDataDir() {
+        return mBase.getDataDir();
+    }
+
+    @Override
     public File getFilesDir() {
         return mBase.getFilesDir();
     }
diff --git a/core/java/android/net/DataUsageRequest.java b/core/java/android/net/DataUsageRequest.java
index 0e46f4c..5e96cc1 100644
--- a/core/java/android/net/DataUsageRequest.java
+++ b/core/java/android/net/DataUsageRequest.java
@@ -34,6 +34,11 @@
     /**
      * @hide
      */
+    public static final String PARCELABLE_KEY = "DataUsageRequest";
+
+    /**
+     * @hide
+     */
     public static final int REQUEST_ID_UNSET = 0;
 
     /**
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 6436e42..2eea940 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -16,10 +16,13 @@
 
 package android.net;
 
+import android.net.DataUsageRequest;
 import android.net.INetworkStatsSession;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.os.IBinder;
+import android.os.Messenger;
 
 /** {@hide} */
 interface INetworkStatsService {
@@ -57,4 +60,11 @@
     /** Advise persistance threshold; may be overridden internally. */
     void advisePersistThreshold(long thresholdBytes);
 
+    /** Registers a callback on data usage. */
+    DataUsageRequest registerDataUsageCallback(String callingPackage,
+            in DataUsageRequest request, in Messenger messenger, in IBinder binder);
+
+    /** Unregisters a callback on data usage. */
+    void unregisterDataUsageRequest(in DataUsageRequest request);
+
 }
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index a4cb703..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -775,10 +775,6 @@
         mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
     }
 
-    public boolean isAttached() {
-        return mOwningView != null && mOwningView.mAttachInfo != null;
-    }
-
     public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) {
         if (mOwningView == null || mOwningView.mAttachInfo == null) {
             throw new IllegalStateException("Cannot start this animator on a detached view!");
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index a106f48..5992f7a 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -64,5 +64,6 @@
     public static final int BASE_NETWORK_AGENT                                      = 0x00081000;
     public static final int BASE_NETWORK_MONITOR                                    = 0x00082000;
     public static final int BASE_NETWORK_FACTORY                                    = 0x00083000;
+    public static final int BASE_ETHERNET                                           = 0x00084000;
     //TODO: define all used protocols
 }
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 14badb7..7a3c598 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -43,13 +43,12 @@
     return env;
 }
 
-static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) {
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
     class AnimationListenerBridge : public AnimationListener {
     public:
-        AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) {
+        AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
             mFinishListener = env->NewGlobalRef(finishListener);
             env->GetJavaVM(&mJvm);
-            mId = id;
         }
 
         virtual ~AnimationListenerBridge() {
@@ -64,7 +63,7 @@
             env->CallStaticVoidMethod(
                     gVectorDrawableAnimatorClassInfo.clazz,
                     gVectorDrawableAnimatorClassInfo.callOnFinished,
-                    mFinishListener, mId);
+                    mFinishListener);
             releaseJavaObject();
         }
 
@@ -77,9 +76,8 @@
 
         JavaVM* mJvm;
         jobject mFinishListener;
-        jint mId;
     };
-    return new AnimationListenerBridge(env, finishListener, id);
+    return new AnimationListenerBridge(env, finishListener);
 }
 
 static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
@@ -144,16 +142,15 @@
     holder->setPropertyDataSource(propertyData, length);
     env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
 }
-static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
     PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
+    // TODO: keep a ref count in finish listener
+    AnimationListener* listener = createAnimationListener(env, finishListener);
     set->start(listener);
 }
 
-static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
-    PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
-    set->reverse(listener);
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+    // TODO: implement reverse
 }
 
 static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
@@ -175,8 +172,8 @@
     {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
     {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
     {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
-    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)start},
-    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)reverse},
+    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
     {"nEnd", "!(J)V", (void*)end},
     {"nReset", "!(J)V", (void*)reset},
 };
@@ -189,7 +186,7 @@
 
     gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
             env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
-            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V");
+            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
     return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
             gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index c9eac79..0926e9b 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -184,7 +184,7 @@
 
 static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
     BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
-    animator->cancel();
+    animator->end();
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index c3afbf6..3869cd2 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -66,7 +66,7 @@
         assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
         assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
@@ -99,7 +99,7 @@
         FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
         bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
@@ -128,15 +128,16 @@
         FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
         bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
                 fileDomainIncludes.iterator().next());
 
         Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+        // Three expected here because of "-journal" and "-wal" files
         assertEquals("Didn't find expected database domain include.",
-                2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
+                3, databaseDomainIncludes.size());
         assertTrue("Invalid path parsed for <include/>",
                 databaseDomainIncludes.contains(
                         new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
@@ -147,6 +148,12 @@
                                 mContext.getDatabasePath("foo").getParentFile(),
                                 "include2.txt-journal")
                                 .getCanonicalPath()));
+        assertTrue("Invalid path parsed for <include/>",
+                databaseDomainIncludes.contains(
+                        new File(
+                                mContext.getDatabasePath("foo").getParentFile(),
+                                "include2.txt-wal")
+                                .getCanonicalPath()));
 
         List<String> sharedPrefDomainIncludes = new ArrayList<String>(
                 includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN));
@@ -168,7 +175,7 @@
                 sharedPrefDomainIncludes.get(2));
 
 
-        assertEquals("Unexpected number of <exclude/>s", 6, excludesSet.size());
+        assertEquals("Unexpected number of <exclude/>s", 7, excludesSet.size());
         // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
         // sort lexicographically.
         List<String> arrayedSet = new ArrayList<String>(excludesSet);
@@ -183,20 +190,24 @@
                         .getCanonicalPath(),
                 arrayedSet.get(1));
         assertEquals("Invalid path parsed for <exclude/>",
-                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-wal")
+                        .getCanonicalPath(),
                 arrayedSet.get(2));
         assertEquals("Invalid path parsed for <exclude/>",
+                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+                arrayedSet.get(3));
+        assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3")
                         .getCanonicalPath(),
-                arrayedSet.get(3));
+                arrayedSet.get(4));
         assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.xml")
                         .getCanonicalPath(),
-                arrayedSet.get(4));
+                arrayedSet.get(5));
         assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude4.xml")
                         .getCanonicalPath(),
-                arrayedSet.get(5));
+                arrayedSet.get(6));
     }
 
     public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
@@ -247,7 +258,7 @@
 
         assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
     }
     public void testDoubleDotInPath_isIgnored() throws Exception {
@@ -261,7 +272,7 @@
 
         assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
     }
 
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 77748a8..af8ccf5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -238,6 +238,9 @@
             mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
         }
         mAnimatedVectorState.mVectorDrawable.draw(canvas);
+        if (isStarted()) {
+            invalidateSelf();
+        }
     }
 
     @Override
@@ -608,6 +611,10 @@
         return mAnimatorSet.isRunning();
     }
 
+    private boolean isStarted() {
+        return mAnimatorSet.isStarted();
+    }
+
     /**
      * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
      */
@@ -619,6 +626,12 @@
     @Override
     public void start() {
         ensureAnimatorSet();
+
+        // If any one of the animator has not ended, do nothing.
+        if (isStarted()) {
+            return;
+        }
+
         mAnimatorSet.start();
         invalidateSelf();
     }
@@ -639,7 +652,6 @@
     @Override
     public void stop() {
         mAnimatorSet.end();
-        invalidateSelf();
     }
 
     /**
@@ -762,9 +774,6 @@
      * @hide
      */
     public static class VectorDrawableAnimator {
-        private static final int NONE = 0;
-        private static final int START_ANIMATION = 1;
-        private static final int REVERSE_ANIMATION = 2;
         private AnimatorListener mListener = null;
         private final LongArray mStartDelays = new LongArray();
         private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -773,14 +782,15 @@
         private boolean mContainsSequentialAnimators = false;
         private boolean mStarted = false;
         private boolean mInitialized = false;
+        private boolean mAnimationPending = false;
         private boolean mIsReversible = false;
         // This needs to be set before parsing starts.
         private boolean mShouldIgnoreInvalidAnim;
         // TODO: Consider using NativeAllocationRegistery to track native allocation
         private final VirtualRefBasePtr mSetRefBasePtr;
+        private WeakReference<RenderNode> mTarget = null;
         private WeakReference<RenderNode> mLastSeenTarget = null;
-        private int mLastListenerId = 0;
-        private int mPendingAnimationAction = NONE;
+
 
         VectorDrawableAnimator() {
             mSetPtr = nCreateAnimatorSet();
@@ -800,7 +810,6 @@
             mInitialized = true;
 
             // Check reversible.
-            mIsReversible = true;
             if (mContainsSequentialAnimators) {
                 mIsReversible = false;
             } else {
@@ -812,6 +821,7 @@
                     }
                 }
             }
+            mIsReversible = true;
         }
 
         private void parseAnimatorSet(AnimatorSet set, long startTime) {
@@ -1032,28 +1042,36 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(DisplayListCanvas canvas) {
-            mLastSeenTarget = new WeakReference<RenderNode>(
-                    RenderNodeAnimatorSetHelper.getTarget(canvas));
-            if (mPendingAnimationAction != NONE) {
+            if (mAnimationPending) {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
                 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                     Log.d(LOGTAG, "Target is set in the next frame");
                 }
-                if (mPendingAnimationAction == START_ANIMATION) {
-                    start();
-                } else if (mPendingAnimationAction == REVERSE_ANIMATION) {
-                    reverse();
-                }
-                mPendingAnimationAction = NONE;
+                mAnimationPending = false;
+                start();
+            } else {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
             }
+
+        }
+
+        private boolean setTarget(RenderNode node) {
+            if (mTarget != null && mTarget.get() != null) {
+                // TODO: Maybe we want to support target change.
+                throw new IllegalStateException("Target already set!");
+            }
+
+            node.addAnimator(this);
+            mTarget = new WeakReference<RenderNode>(node);
+            return true;
         }
 
         private boolean useLastSeenTarget() {
-            if (mLastSeenTarget != null) {
-                final RenderNode target = mLastSeenTarget.get();
-                if (target != null && target.isAttached()) {
-                    target.addAnimator(this);
-                    return true;
-                }
+            if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
+                setTarget(mLastSeenTarget.get());
+                return true;
             }
             return false;
         }
@@ -1063,8 +1081,12 @@
                 return;
             }
 
+            if (mStarted) {
+                return;
+            }
+
             if (!useLastSeenTarget()) {
-                mPendingAnimationAction = START_ANIMATION;
+                mAnimationPending = true;
                 return;
             }
 
@@ -1072,45 +1094,38 @@
                 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
             }
 
-            mStarted = true;
-            nStart(mSetPtr, this, ++mLastListenerId);
+           nStart(mSetPtr, this);
             if (mListener != null) {
                 mListener.onAnimationStart(null);
             }
+            mStarted = true;
         }
 
         public void end() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
+            if (mInitialized && mStarted) {
                 nEnd(mSetPtr);
+                onAnimationEnd();
             }
         }
 
-        public void reset() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
-                nReset(mSetPtr);
+        void reset() {
+            if (!mInitialized) {
+                return;
             }
+            // TODO: Need to implement reset.
+            Log.w(LOGTAG, "Reset is yet to be implemented");
+            nReset(mSetPtr);
         }
 
         // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
         // animators or when the animator set has a start delay
         void reverse() {
-            if (!mIsReversible || !mInitialized) {
+            if (!mIsReversible) {
                 return;
             }
-            if (!useLastSeenTarget()) {
-                mPendingAnimationAction = REVERSE_ANIMATION;
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
-            }
-            mStarted = true;
-            nReverse(mSetPtr, this, ++mLastListenerId);
-            if (mListener != null) {
-                mListener.onAnimationStart(null);
-            }
+            // TODO: Need to support reverse (non-public API)
+            Log.w(LOGTAG, "Reverse is yet to be implemented");
+            nReverse(mSetPtr, this);
         }
 
         public long getAnimatorNativePtr() {
@@ -1140,22 +1155,20 @@
             mListener = null;
         }
 
-        private void onAnimationEnd(int listenerId) {
-            if (listenerId != mLastListenerId) {
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "on finished called from native");
-            }
+        private void onAnimationEnd() {
             mStarted = false;
             if (mListener != null) {
                 mListener.onAnimationEnd(null);
             }
+            mTarget = null;
         }
 
         // onFinished: should be called from native
-        private static void callOnFinished(VectorDrawableAnimator set, int id) {
-            set.onAnimationEnd(id);
+        private static void callOnFinished(VectorDrawableAnimator set) {
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "on finished called from native");
+            }
+            set.onAnimationEnd();
         }
     }
 
@@ -1175,8 +1188,8 @@
     private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
             float endValue);
     private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
-    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id);
-    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id);
+    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
     private static native void nEnd(long animatorSetPtr);
     private static native void nReset(long animatorSetPtr);
 }
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 294edb6..7bd2b24 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -33,7 +33,6 @@
 
 BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
         : mTarget(nullptr)
-        , mStagingTarget(nullptr)
         , mFinalValue(finalValue)
         , mDeltaValue(0)
         , mFromValue(0)
@@ -43,8 +42,7 @@
         , mStartTime(0)
         , mDuration(300)
         , mStartDelay(0)
-        , mMayRunAsync(true)
-        , mPlayTime(0) {
+        , mMayRunAsync(true) {
 }
 
 BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -83,129 +81,26 @@
 }
 
 void BaseRenderNodeAnimator::attach(RenderNode* target) {
-    mStagingTarget = target;
+    mTarget = target;
     onAttached();
 }
 
-void BaseRenderNodeAnimator::start() {
-    mStagingPlayState = PlayState::Running;
-    mStagingRequests.push_back(Request::Start);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::cancel() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Cancel);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reset() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Reset);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reverse() {
-    mStagingPlayState = PlayState::Reversing;
-    mStagingRequests.push_back(Request::Reverse);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::end() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::End);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
-    switch (request) {
-    case Request::Start:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : 0;
-        mPlayState = PlayState::Running;
-        break;
-    case Request::Reverse:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : mDuration;
-        mPlayState = PlayState::Reversing;
-        break;
-    case Request::Reset:
-        mPlayTime = 0;
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::Cancel:
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::End:
-        mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
-        mPlayState = PlayState::Finished;
-        break;
-    default:
-        LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
-    };
-}
-
 void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
-    if (mStagingTarget) {
-        RenderNode* oldTarget = mTarget;
-        mTarget = mStagingTarget;
-        mStagingTarget = nullptr;
-        if (oldTarget && oldTarget != mTarget) {
-            oldTarget->onAnimatorTargetChanged(this);
-        }
-    }
-
     if (!mHasStartValue) {
         doSetStartValue(getValue(mTarget));
     }
-
-    if (!mStagingRequests.empty()) {
-        // Keep track of the play state and play time before they are changed when
-        // staging requests are resolved.
-        nsecs_t currentPlayTime = mPlayTime;
-        PlayState prevFramePlayState = mPlayState;
-
-        // Resolve staging requests one by one.
-        for (Request request : mStagingRequests) {
-            resolveStagingRequest(request);
+    if (mStagingPlayState > mPlayState) {
+        if (mStagingPlayState == PlayState::Restarted) {
+            mStagingPlayState = PlayState::Running;
         }
-        mStagingRequests.clear();
-
-        if (mStagingPlayState == PlayState::Finished) {
-            // Set the staging play time and end the animation
-            updatePlayTime(mPlayTime);
+        mPlayState = mStagingPlayState;
+        // Oh boy, we're starting! Man the battle stations!
+        if (mPlayState == PlayState::Running) {
+            transitionToRunning(context);
+        } else if (mPlayState == PlayState::Finished) {
             callOnFinishedListener(context);
-        } else if (mStagingPlayState == PlayState::Running
-                || mStagingPlayState == PlayState::Reversing) {
-            bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
-            if (prevFramePlayState != mStagingPlayState) {
-                transitionToRunning(context);
-            }
-            if (changed) {
-                // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
-                // requested from UI thread). It is achieved by modifying mStartTime, such that
-                // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
-                // case of reversing)
-                nsecs_t currentFrameTime = context.frameTimeMs();
-                if (mPlayState == PlayState::Reversing) {
-                    // Reverse is not supported for animations with a start delay, so here we
-                    // assume no start delay.
-                    mStartTime = currentFrameTime  - (mDuration - mPlayTime);
-                } else {
-                    // Animation should play forward
-                    if (mPlayTime == 0) {
-                        // If the request is to start from the beginning, include start delay.
-                        mStartTime = currentFrameTime + mStartDelay;
-                    } else {
-                        // If the request is to seek to a non-zero play time, then we skip start
-                        // delay.
-                        mStartTime = currentFrameTime - mPlayTime;
-                    }
-                }
-            }
         }
     }
-    onPushStaging();
 }
 
 void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
@@ -241,37 +136,37 @@
 
     // This should be set before setValue() so animators can query this time when setValue
     // is called.
-    nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
-    bool finished = updatePlayTime(currentPlayTime);
-    if (finished && mPlayState != PlayState::Finished) {
-        mPlayState = PlayState::Finished;
-        callOnFinishedListener(context);
-    }
-    return finished;
-}
+    nsecs_t currentFrameTime = context.frameTimeMs();
+    onPlayTimeChanged(currentFrameTime - mStartTime);
 
-bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
-    mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
-    onPlayTimeChanged(mPlayTime);
     // If BaseRenderNodeAnimator is handling the delay (not typical), then
     // because the staging properties reflect the final value, we always need
     // to call setValue even if the animation isn't yet running or is still
     // being delayed as we need to override the staging value
-    if (playTime < 0) {
+    if (mStartTime > context.frameTimeMs()) {
         setValue(mTarget, mFromValue);
         return false;
     }
 
     float fraction = 1.0f;
-    if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
-        fraction = mPlayTime / (float) mDuration;
+
+    if (mPlayState == PlayState::Running && mDuration > 0) {
+        fraction = (float)(currentFrameTime - mStartTime) / mDuration;
     }
-    fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
+    if (fraction >= 1.0f) {
+        fraction = 1.0f;
+        mPlayState = PlayState::Finished;
+    }
 
     fraction = mInterpolator->interpolate(fraction);
     setValue(mTarget, mFromValue + (mDeltaValue * fraction));
 
-    return playTime >= mDuration;
+    if (mPlayState == PlayState::Finished) {
+        callOnFinishedListener(context);
+        return true;
+    }
+
+    return false;
 }
 
 void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
@@ -320,36 +215,18 @@
 
 void RenderPropertyAnimator::onAttached() {
     if (!mHasStartValue
-            && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
-        setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)());
+            && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+        setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)());
     }
 }
 
 void RenderPropertyAnimator::onStagingPlayStateChanged() {
     if (mStagingPlayState == PlayState::Running) {
-        if (mStagingTarget) {
-            (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
-        } else {
-            // In the case of start delay where stagingTarget has been sync'ed over and null'ed
-            // we delay the properties update to push staging.
-            mShouldUpdateStagingProperties = true;
-        }
+        (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
     } else if (mStagingPlayState == PlayState::Finished) {
         // We're being canceled, so make sure that whatever values the UI thread
         // is observing for us is pushed over
-        mShouldSyncPropertyFields = true;
-    }
-}
-
-void RenderPropertyAnimator::onPushStaging() {
-    if (mShouldUpdateStagingProperties) {
-        (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
-        mShouldUpdateStagingProperties = false;
-    }
-
-    if (mShouldSyncPropertyFields) {
         mTarget->setPropertyFieldsDirty(dirtyMask());
-        mShouldSyncPropertyFields = false;
     }
 }
 
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index fdae0f3..2c9c9c3 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,8 +24,6 @@
 
 #include "utils/Macros.h"
 
-#include <vector>
-
 namespace android {
 namespace uirenderer {
 
@@ -61,14 +59,14 @@
         mMayRunAsync = mayRunAsync;
     }
     bool mayRunAsync() { return mMayRunAsync; }
-    ANDROID_API void start();
-    ANDROID_API void reset();
-    ANDROID_API void reverse();
-    // Terminates the animation at its current progress.
-    ANDROID_API void cancel();
-
-    // Terminates the animation and skip to the end of the animation.
-    ANDROID_API void end();
+    ANDROID_API void start() {
+        if (mStagingPlayState == PlayState::NotStarted) {
+            mStagingPlayState = PlayState::Running;
+        } else {
+            mStagingPlayState = PlayState::Restarted;
+        }
+        onStagingPlayStateChanged(); }
+    ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
 
     void attach(RenderNode* target);
     virtual void onAttached() {}
@@ -76,42 +74,36 @@
     void pushStaging(AnimationContext& context);
     bool animate(AnimationContext& context);
 
-    bool isRunning() { return mPlayState == PlayState::Running
-            || mPlayState == PlayState::Reversing; }
+    bool isRunning() { return mPlayState == PlayState::Running; }
     bool isFinished() { return mPlayState == PlayState::Finished; }
     float finalValue() { return mFinalValue; }
 
     ANDROID_API virtual uint32_t dirtyMask() = 0;
 
     void forceEndNow(AnimationContext& context);
-    RenderNode* target() { return mTarget; }
-    RenderNode* stagingTarget() { return mStagingTarget; }
 
 protected:
     // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
     // thread and Render Thread animation state, respectively.
     // From the UI thread, mStagingPlayState transition looks like
-    // NotStarted -> Running/Reversing -> Finished
-    //                ^                     |
-    //                |                     |
-    //                ----------------------
+    // NotStarted -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //            Restarted <------
     // Note: For mStagingState, the Finished state (optional) is only set when the animation is
     // terminated by user.
     //
     // On Render Thread, mPlayState transition:
-    // NotStart -> Running/Reversing-> Finished
-    //                ^                 |
-    //                |                 |
-    //                ------------------
-    // Note that if the animation is in Running/Reversing state, calling start or reverse again
-    // would do nothing if the animation has the same play direction as the request; otherwise,
-    // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
+    // NotStart -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //                -------------
 
     enum class PlayState {
         NotStarted,
         Running,
-        Reversing,
         Finished,
+        Restarted,
     };
 
     BaseRenderNodeAnimator(float finalValue);
@@ -119,15 +111,14 @@
 
     virtual float getValue(RenderNode* target) const = 0;
     virtual void setValue(RenderNode* target, float value) = 0;
+    RenderNode* target() { return mTarget; }
 
     void callOnFinishedListener(AnimationContext& context);
 
     virtual void onStagingPlayStateChanged() {}
     virtual void onPlayTimeChanged(nsecs_t playTime) {}
-    virtual void onPushStaging() {}
 
     RenderNode* mTarget;
-    RenderNode* mStagingTarget;
 
     float mFinalValue;
     float mDeltaValue;
@@ -141,28 +132,13 @@
     nsecs_t mDuration;
     nsecs_t mStartDelay;
     bool mMayRunAsync;
-    // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
-    // the beginning of the animation, will reach mDuration at the end of an animation.
-    nsecs_t mPlayTime;
 
     sp<AnimationListener> mListener;
 
 private:
-    enum class Request {
-        Start,
-        Reverse,
-        Reset,
-        Cancel,
-        End
-    };
     inline void checkMutable();
     virtual void transitionToRunning(AnimationContext& context);
     void doSetStartValue(float value);
-    bool updatePlayTime(nsecs_t playTime);
-    void resolveStagingRequest(Request request);
-
-    std::vector<Request> mStagingRequests;
-
 };
 
 class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -191,7 +167,6 @@
     virtual void setValue(RenderNode* target, float value) override;
     virtual void onAttached() override;
     virtual void onStagingPlayStateChanged() override;
-    virtual void onPushStaging() override;
 
 private:
     typedef bool (RenderProperties::*SetFloatProperty)(float value);
@@ -201,8 +176,6 @@
     const PropertyAccessors* mPropertyAccess;
 
     static const PropertyAccessors PROPERTY_ACCESSOR_LUT[];
-    bool mShouldSyncPropertyFields = false;
-    bool mShouldUpdateStagingProperties = false;
 };
 
 class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 2198fcc..cd30b18 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,8 +27,9 @@
 
 using namespace std;
 
-static void detach(sp<BaseRenderNodeAnimator>& animator) {
+static void unref(BaseRenderNodeAnimator* animator) {
     animator->detach();
+    animator->decStrong(nullptr);
 }
 
 AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -37,28 +38,14 @@
 }
 
 AnimatorManager::~AnimatorManager() {
-    for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
-    for_each(mAnimators.begin(), mAnimators.end(), detach);
+    for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+    for_each(mAnimators.begin(), mAnimators.end(), unref);
 }
 
 void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    RenderNode* stagingTarget = animator->stagingTarget();
-    if (stagingTarget == &mParent) {
-        return;
-    }
-    mNewAnimators.emplace_back(animator.get());
-    // If the animator is already attached to other RenderNode, remove it from that RenderNode's
-    // new animator list. This ensures one animator only ends up in one newAnimatorList during one
-    // frame, even when it's added multiple times to multiple targets.
-    if (stagingTarget) {
-        stagingTarget->removeAnimator(animator);
-    }
+    animator->incStrong(nullptr);
     animator->attach(&mParent);
-}
-
-void AnimatorManager::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    mNewAnimators.erase(std::remove(mNewAnimators.begin(), mNewAnimators.end(), animator),
-            mNewAnimators.end());
+    mNewAnimators.push_back(animator.get());
 }
 
 void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -69,40 +56,38 @@
             &mParent, mParent.getName());
 }
 
+template<typename T>
+static void move_all(T& source, T& dest) {
+    dest.reserve(source.size() + dest.size());
+    for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+        dest.push_back(*it);
+    }
+    source.clear();
+}
+
 void AnimatorManager::pushStaging() {
     if (mNewAnimators.size()) {
         LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
                 "Trying to start new animators on %p (%s) without an animation handle!",
                 &mParent, mParent.getName());
-
-        // Only add new animators that are not already in the mAnimators list
-        for (auto& anim : mNewAnimators) {
-            if (anim->target() != &mParent) {
-                mAnimators.push_back(std::move(anim));
-            }
-        }
-        mNewAnimators.clear();
+        // Since this is a straight move, we don't need to inc/dec the ref count
+        move_all(mNewAnimators, mAnimators);
     }
-    for (auto& animator : mAnimators) {
-        animator->pushStaging(mAnimationHandle->context());
+    for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+        (*it)->pushStaging(mAnimationHandle->context());
     }
 }
 
-void AnimatorManager::onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
-    LOG_ALWAYS_FATAL_IF(animator->target() == &mParent, "Target has not been changed");
-    mAnimators.erase(std::remove(mAnimators.begin(), mAnimators.end(), animator), mAnimators.end());
-}
-
 class AnimateFunctor {
 public:
     AnimateFunctor(TreeInfo& info, AnimationContext& context)
             : dirtyMask(0), mInfo(info), mContext(context) {}
 
-    bool operator() (sp<BaseRenderNodeAnimator>& animator) {
+    bool operator() (BaseRenderNodeAnimator* animator) {
         dirtyMask |= animator->dirtyMask();
         bool remove = animator->animate(mContext);
         if (remove) {
-            animator->detach();
+            animator->decStrong(nullptr);
         } else {
             if (animator->isRunning()) {
                 mInfo.out.hasAnimations = true;
@@ -144,18 +129,20 @@
 
 uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
     AnimateFunctor functor(info, mAnimationHandle->context());
-    auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
     mAnimators.erase(newEnd, mAnimators.end());
     mAnimationHandle->notifyAnimationsRan();
     mParent.mProperties.updateMatrix();
     return functor.dirtyMask;
 }
 
-static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
-    animator->cancel();
+static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
+    animator->end();
     if (animator->listener()) {
-        animator->listener()->onAnimationFinished(animator.get());
+        animator->listener()->onAnimationFinished(animator);
     }
+    animator->decStrong(nullptr);
 }
 
 void AnimatorManager::endAllStagingAnimators() {
@@ -170,8 +157,9 @@
 public:
     EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
 
-    void operator() (sp<BaseRenderNodeAnimator>& animator) {
+    void operator() (BaseRenderNodeAnimator* animator) {
         animator->forceEndNow(mContext);
+        animator->decStrong(nullptr);
     }
 
 private:
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index 61f6179..fb75eb8 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -39,13 +39,11 @@
     ~AnimatorManager();
 
     void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
-    void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
 
     void setAnimationHandle(AnimationHandle* handle);
     bool hasAnimationHandle() { return mAnimationHandle; }
 
     void pushStaging();
-    void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator);
 
     // Returns the combined dirty mask of all animators run
     uint32_t animate(TreeInfo& info);
@@ -68,8 +66,9 @@
     AnimationHandle* mAnimationHandle;
 
     // To improve the efficiency of resizing & removing from the vector
-    std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
-    std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+    // use manual ref counting instead of sp<>.
+    std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+    std::vector<BaseRenderNodeAnimator*> mAnimators;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index b29f91f..eca1afcc 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -17,8 +17,6 @@
 #include "PropertyValuesAnimatorSet.h"
 #include "RenderNode.h"
 
-#include <algorithm>
-
 namespace android {
 namespace uirenderer {
 
@@ -55,26 +53,16 @@
 }
 
 void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
-    if (playTime == 0 && mDuration > 0) {
-        // Reset all the animators
-        for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
-            // Note that this set may containing animators modifying the same property, so when we
-            // reset the animators, we need to make sure the animators that end the first will
-            // have the final say on what the property value should be.
-            (*it)->setFraction(0);
-        }
-    } else if (playTime >= mDuration) {
-        // Skip all the animators to end
-        for (auto& anim : mAnimators) {
-            anim->setFraction(1);
-        }
-    } else {
-        for (auto& anim : mAnimators) {
-            anim->setCurrentPlayTime(playTime);
-        }
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        mAnimators[i]->setCurrentPlayTime(playTime);
     }
 }
 
+void PropertyValuesAnimatorSet::reset() {
+    // TODO: implement reset through adding a play state because we need to support reset() even
+    // during an animation run.
+}
+
 void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
@@ -82,23 +70,20 @@
 }
 
 void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
-    init();
-    mOneShotListener = listener;
-    BaseRenderNodeAnimator::reverse();
+// TODO: implement reverse
 }
 
 void PropertyValuesAnimatorSet::init() {
     if (mInitialized) {
         return;
     }
-
-    // Sort the animators by their total duration. Note that all the animators in the set start at
-    // the same time, so the ones with longer total duration (which includes start delay) will
-    // be the ones that end later.
-    std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
-        return a->getTotalDuration() < b->getTotalDuration();
-    });
-    mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
+    nsecs_t maxDuration = 0;
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        if (maxDuration < mAnimators[i]->getTotalDuration()) {
+            maxDuration = mAnimators[i]->getTotalDuration();
+        }
+    }
+    mDuration = maxDuration;
     mInitialized = true;
 }
 
@@ -121,19 +106,18 @@
 void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
     if (playTime >= mStartDelay && playTime < mTotalDuration) {
          nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
-         float fraction = currentIterationPlayTime / (float) mDuration;
-         setFraction(fraction);
+         mLatestFraction = currentIterationPlayTime / (float) mDuration;
     } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
-        // This makes sure we only set the fraction = 1 once. It is needed because there might
-        // be another animator modifying the same property after this animator finishes, we need
-        // to make sure we don't set conflicting values on the same property within one frame.
-        setFraction(1.0f);
+        mLatestFraction = 1.0f;
+    } else {
+        return;
     }
+
+    setFraction(mLatestFraction);
 }
 
 void PropertyAnimator::setFraction(float fraction) {
-    mLatestFraction = fraction;
-    float interpolatedFraction = mInterpolator->interpolate(fraction);
+    float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
     mPropertyValuesHolder->setFraction(interpolatedFraction);
 }
 
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index c7ae7c0..4c7ce52 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -50,6 +50,7 @@
 
     void start(AnimationListener* listener);
     void reverse(AnimationListener* listener);
+    void reset();
 
     void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
             Interpolator* interpolators, int64_t startDelays,
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 9ac76a4..bade216 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -218,10 +218,6 @@
     mAnimatorManager.addAnimator(animator);
 }
 
-void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    mAnimatorManager.removeAnimator(animator);
-}
-
 void RenderNode::damageSelf(TreeInfo& info) {
     if (isRenderable()) {
         if (properties().getClipDamageToBounds()) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index e037645..f248de54 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -187,12 +187,6 @@
 
     // UI thread only!
     ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
-    void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
-
-    // This can only happen during pushStaging()
-    void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
-        mAnimatorManager.onAnimatorTargetChanged(animator);
-    }
 
     AnimatorManager& animators() { return mAnimatorManager; }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 287c904..32543c8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -16,8 +16,9 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
 import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.app.Activity;
@@ -40,7 +41,8 @@
     public static final String TAG = "PickFragment";
 
     private int mAction;
-    private @OpType int mOperationType;
+    // Only legal values are OPERATION_COPY, OPERATION_MOVE, and unset (OPERATION_UNKNOWN).
+    private @OpType int mCopyOperationSubType = OPERATION_UNKNOWN;
     private DocumentInfo mPickTarget;
     private View mContainer;
     private Button mPick;
@@ -97,10 +99,11 @@
     /**
      * @param action Which action defined in State is the picker shown for.
      */
-    public void setPickTarget(int action, @OpType int operationType, DocumentInfo pickTarget) {
-        checkArgument(operationType == OPERATION_COPY || operationType == OPERATION_MOVE);
+    public void setPickTarget(
+            int action, @OpType int copyOperationSubType, DocumentInfo pickTarget) {
+        checkArgument(copyOperationSubType != OPERATION_DELETE);
         mAction = action;
-        mOperationType = operationType;
+        mCopyOperationSubType = copyOperationSubType;
         mPickTarget = pickTarget;
         if (mContainer != null) {
             updateView();
@@ -117,7 +120,7 @@
                 mCancel.setVisibility(View.GONE);
                 break;
             case State.ACTION_PICK_COPY_DESTINATION:
-                mPick.setText(mOperationType == OPERATION_MOVE
+                mPick.setText(mCopyOperationSubType == OPERATION_MOVE
                         ? R.string.button_move : R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 81a0635..0948ab1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -30,6 +30,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
 
 import java.lang.annotation.Retention;
@@ -93,9 +94,10 @@
 
     /**
      * This is basically a sub-type for the copy operation. It can be either COPY or MOVE.
-     * The only legal values are: OPERATION_COPY, OPERATION_MOVE.
+     * The only legal values, if set, are: OPERATION_COPY, OPERATION_MOVE. Other pick
+     * operations don't use this. In those cases OPERATION_UNKNOWN is also legal.
      */
-    public @OpType int copyOperationSubType;
+    public @OpType int copyOperationSubType = FileOperationService.OPERATION_UNKNOWN;
 
     /** Current user navigation stack; empty implies recents. */
     public DocumentStack stack = new DocumentStack();
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index bed8f1b..858f487 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -50,30 +50,6 @@
             android:layout_height="0dp"
             android:layout_weight="1" />
 
-    <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingEnd="8dp"
-            android:gravity="end">
+    <include layout="@layout/qs_detail_buttons" />
 
-        <TextView
-                android:id="@android:id/button2"
-                style="@style/QSBorderlessButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
-                android:minWidth="132dp"
-                android:textAppearance="@style/TextAppearance.QS.DetailButton"
-                android:focusable="true" />
-
-        <TextView
-                android:id="@android:id/button1"
-                style="@style/QSBorderlessButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:minWidth="88dp"
-                android:textAppearance="@style/TextAppearance.QS.DetailButton"
-                android:focusable="true"/>
-
-    </LinearLayout>
 </com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml
new file mode 100644
index 0000000..03ed62b
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_buttons.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingEnd="8dp"
+    android:gravity="end">
+
+    <TextView
+        android:id="@android:id/button2"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:minWidth="132dp"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton"
+        android:focusable="true" />
+
+    <TextView
+        android:id="@android:id/button1"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minWidth="88dp"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton"
+        android:focusable="true"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 26152cd..8df2c280 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -32,7 +32,6 @@
     >
 
     <LinearLayout
-        android:id="@+id/expanded_group"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
         android:gravity="center"
@@ -80,12 +79,12 @@
 
         </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 
-        <ImageView
+        <com.android.systemui.statusbar.phone.ExpandableIndicator
+            android:id="@+id/expand_indicator"
             android:layout_width="48dp"
             android:layout_height="48dp"
-            android:padding="12dp"
-            android:src="@drawable/ic_expand_less"
-            android:tint="@android:color/white" />
+            android:padding="12dp" />
+
     </LinearLayout>
 
     <TextView
@@ -109,6 +108,7 @@
         android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
+        android:layout_marginTop="4dp"
         android:layout_marginStart="16dp"
         android:gravity="start"
         android:orientation="vertical">
@@ -116,7 +116,6 @@
             android:id="@+id/date_time_group"
             android:layout_width="wrap_content"
             android:layout_height="19dp"
-            android:layout_marginTop="4dp"
             android:orientation="horizontal">
 
             <include layout="@layout/split_clock_view"
diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
new file mode 100644
index 0000000..efe63d7
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<!-- extends LinearLayout -->
+<com.android.systemui.tuner.TunerZenModePanel
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tuner_zen_mode_panel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:visibility="gone"
+    android:orientation="vertical" >
+
+    <View
+        android:id="@+id/zen_embedded_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_marginBottom="12dp"
+        android:layout_marginTop="8dp"
+        android:background="@color/qs_tile_divider" />
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:layout_marginStart="16dp"
+        android:id="@+id/tuner_zen_switch"
+        layout="@layout/qs_detail_header" />
+
+    <include layout="@layout/zen_mode_panel" />
+
+    <include
+        android:id="@+id/tuner_zen_buttons"
+        layout="@layout/qs_detail_buttons" />
+
+</com.android.systemui.tuner.TunerZenModePanel>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 34796cd..e4effd4 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -46,7 +46,7 @@
         <include layout="@layout/volume_zen_footer" />
 
         <!-- Only shown from Tuner setting -->
-        <include layout="@layout/zen_mode_panel" />
+        <include layout="@layout/tuner_zen_mode_panel" />
     </LinearLayout>
 
 </RelativeLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aed5ab2..b8044ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -166,6 +166,7 @@
     <dimen name="qs_date_alarm_anim_translation">26dp</dimen>
     <dimen name="qs_date_collapsed_text_size">14sp</dimen>
     <dimen name="qs_date_text_size">16sp</dimen>
+    <dimen name="qs_header_gear_translation">120dp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fa5b1a9..6135dc6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -810,12 +810,6 @@
     <!-- Interruption level: Alarms only.  Optimized for narrow two-line display. [CHAR LIMIT=40] -->
     <string name="interruption_level_alarms_twoline">Alarms\nonly</string>
 
-    <!-- Interruption level: All interruptions. [CHAR LIMIT=40] -->
-    <string name="interruption_level_all">All</string>
-
-    <!-- Interruption level: All interruptions.  Optimized for narrow two-line display. [CHAR LIMIT=40] -->
-    <string name="interruption_level_all_twoline">All\n</string>
-
     <!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
     <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f208470..d0f7e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -17,12 +17,10 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Space;
 import com.android.systemui.R;
@@ -103,7 +101,7 @@
 
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
-        private final ImageView mDownArrow;
+        private final Space mEndSpacer;
 
         public HeaderTileLayout(Context context) {
             super(context);
@@ -112,16 +110,10 @@
             setGravity(Gravity.CENTER_VERTICAL);
             setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
 
-            int padding =
-                    mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
-            mDownArrow = new ImageView(context);
-            mDownArrow.setImageResource(R.drawable.ic_expand_more);
-            mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
-                    android.R.color.white, null)));
-            mDownArrow.setLayoutParams(generateLayoutParams());
-            mDownArrow.setPadding(padding, padding, padding, padding);
+            mEndSpacer = new Space(context);
+            mEndSpacer.setLayoutParams(generateLayoutParams());
             updateDownArrowMargin();
-            addView(mDownArrow);
+            addView(mEndSpacer);
             setOrientation(LinearLayout.HORIZONTAL);
         }
 
@@ -132,10 +124,10 @@
         }
 
         private void updateDownArrowMargin() {
-            LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
+            LayoutParams params = (LayoutParams) mEndSpacer.getLayoutParams();
             params.setMarginStart(mContext.getResources().getDimensionPixelSize(
                     R.dimen.qs_expand_margin));
-            mDownArrow.setLayoutParams(params);
+            mEndSpacer.setLayoutParams(params);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
new file mode 100644
index 0000000..5e6b52b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.qs;
+
+import android.animation.Keyframe;
+import android.util.MathUtils;
+import android.util.Property;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class, that handles similar properties as animators (delay, interpolators)
+ * but can have a float input as to the amount they should be in effect.  This allows
+ * easier animation that tracks input.
+ *
+ * All "delays" and "times" are as fractions from 0-1.
+ */
+public class TouchAnimator {
+
+    private final Object[] mTargets;
+    private final Property[] mProperties;
+    private final KeyframeSet[] mKeyframeSets;
+    private final float mStartDelay;
+    private final float mEndDelay;
+    private final float mSpan;
+    private final Interpolator mInterpolator;
+    private final Listener mListener;
+    private float mLastT;
+
+    private TouchAnimator(Object[] targets, Property[] properties, KeyframeSet[] keyframeSets,
+            float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
+        mTargets = targets;
+        mProperties = properties;
+        mKeyframeSets = keyframeSets;
+        mStartDelay = startDelay;
+        mEndDelay = endDelay;
+        mSpan = (1 - mEndDelay - mStartDelay);
+        mInterpolator = interpolator;
+        mListener = listener;
+    }
+
+    public void setPosition(float fraction) {
+        float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
+        if (mInterpolator != null) {
+            t = mInterpolator.getInterpolation(t);
+        }
+        if (mListener != null) {
+            if (mLastT == 0 || mLastT == 1) {
+                if (t != 0) {
+                    mListener.onAnimationStarted();
+                }
+            } else if (t == 1) {
+                mListener.onAnimationAtEnd();
+            } else if (t == 0) {
+                mListener.onAnimationAtStart();
+            }
+            mLastT = t;
+        }
+        for (int i = 0; i < mTargets.length; i++) {
+            Object value = mKeyframeSets[i].getValue(t);
+            mProperties[i].set(mTargets[i], value);
+        }
+    }
+
+    public static class ListenerAdapter implements Listener {
+        @Override
+        public void onAnimationAtStart() { }
+
+        @Override
+        public void onAnimationAtEnd() { }
+
+        @Override
+        public void onAnimationStarted() { }
+    }
+
+    public interface Listener {
+        /**
+         * Called when the animator moves into a position of "0". Start and end delays are
+         * taken into account, so this position may cover a range of fractional inputs.
+         */
+        void onAnimationAtStart();
+
+        /**
+         * Called when the animator moves into a position of "0". Start and end delays are
+         * taken into account, so this position may cover a range of fractional inputs.
+         */
+        void onAnimationAtEnd();
+
+        /**
+         * Called when the animator moves out of the start or end position and is in a transient
+         * state.
+         */
+        void onAnimationStarted();
+    }
+
+    public static class Builder {
+        private List<Object> mTargets = new ArrayList<>();
+        private List<Property> mProperties = new ArrayList<>();
+        private List<KeyframeSet> mValues = new ArrayList<>();
+
+        private float mStartDelay;
+        private float mEndDelay;
+        private Interpolator mInterpolator;
+        private Listener mListener;
+
+        public Builder addFloat(Object target, String property, float... values) {
+            add(target, property, KeyframeSet.ofFloat(values));
+            return this;
+        }
+
+        public Builder addInt(Object target, String property, int... values) {
+            add(target, property, KeyframeSet.ofInt(values));
+            return this;
+        }
+
+        private void add(Object target, String property, KeyframeSet keyframeSet) {
+            mTargets.add(target);
+            // TODO: Optimize the properties here, to use those in View when possible.
+            mProperties.add(Property.of(target.getClass(), float.class, property));
+            mValues.add(keyframeSet);
+        }
+
+        public Builder setStartDelay(float startDelay) {
+            mStartDelay = startDelay;
+            return this;
+        }
+
+        public Builder setEndDelay(float endDelay) {
+            mEndDelay = endDelay;
+            return this;
+        }
+
+        public Builder setInterpolator(Interpolator intepolator) {
+            mInterpolator = intepolator;
+            return this;
+        }
+
+        public Builder setListener(Listener listener) {
+            mListener = listener;
+            return this;
+        }
+
+        public TouchAnimator build() {
+            return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
+                    mProperties.toArray(new Property[mProperties.size()]),
+                    mValues.toArray(new KeyframeSet[mValues.size()]),
+                    mStartDelay, mEndDelay, mInterpolator, mListener);
+        }
+    }
+
+    private static abstract class KeyframeSet {
+
+        private final Keyframe[] mKeyframes;
+
+        public KeyframeSet(Keyframe[] keyframes) {
+            mKeyframes = keyframes;
+        }
+
+        Object getValue(float fraction) {
+            int i;
+            for (i = 1; i < mKeyframes.length && fraction > mKeyframes[i].getFraction(); i++) ;
+            Keyframe first = mKeyframes[i - 1];
+            Keyframe second = mKeyframes[i];
+            float amount = (fraction - first.getFraction())
+                    / (second.getFraction() - first.getFraction());
+            return interpolate(first, second, amount);
+        }
+
+        protected abstract Object interpolate(Keyframe first, Keyframe second, float amount);
+
+        public static KeyframeSet ofInt(int... values) {
+            int numKeyframes = values.length;
+            Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+            if (numKeyframes == 1) {
+                keyframes[0] = Keyframe.ofInt(0f);
+                keyframes[1] = Keyframe.ofInt(1f, values[0]);
+            } else {
+                keyframes[0] = Keyframe.ofInt(0f, values[0]);
+                for (int i = 1; i < numKeyframes; ++i) {
+                    keyframes[i] = Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+                }
+            }
+            return new IntKeyframeSet(keyframes);
+        }
+
+        public static KeyframeSet ofFloat(float... values) {
+            int numKeyframes = values.length;
+            Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+            if (numKeyframes == 1) {
+                keyframes[0] = Keyframe.ofFloat(0f);
+                keyframes[1] = Keyframe.ofFloat(1f, values[0]);
+            } else {
+                keyframes[0] = Keyframe.ofFloat(0f, values[0]);
+                for (int i = 1; i < numKeyframes; ++i) {
+                    keyframes[i] = Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                }
+            }
+            return new FloatKeyframeSet(keyframes);
+        }
+    }
+
+    public static class FloatKeyframeSet extends KeyframeSet {
+        public FloatKeyframeSet(Keyframe[] keyframes) {
+            super(keyframes);
+        }
+
+        @Override
+        protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+            float firstFloat = (float) first.getValue();
+            float secondFloat = (float) second.getValue();
+            return firstFloat + (secondFloat - firstFloat) * amount;
+        }
+    }
+
+    public static class IntKeyframeSet extends KeyframeSet {
+        public IntKeyframeSet(Keyframe[] keyframes) {
+            super(keyframes);
+        }
+
+        @Override
+        protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+            int firstFloat = (int) first.getValue();
+            int secondFloat = (int) second.getValue();
+            return (int) (firstFloat + (secondFloat - firstFloat) * amount);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
new file mode 100644
index 0000000..8c7c71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import com.android.systemui.R;
+
+public class ExpandableIndicator extends ImageView {
+
+    private boolean mExpanded;
+
+    public ExpandableIndicator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+                : R.drawable.ic_volume_expand_animation;
+        setImageResource(res);
+    }
+
+    public void setExpanded(boolean expanded) {
+        if (expanded == mExpanded) return;
+        mExpanded = expanded;
+        final int res = mExpanded ? R.drawable.ic_volume_expand_animation
+                : R.drawable.ic_volume_collapse_animation;
+        // workaround to reset drawable
+        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext()
+                .getDrawable(res).getConstantState().newDrawable();
+        setImageDrawable(avd);
+        avd.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 97d7dd5..6698076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -79,7 +79,7 @@
     private Drawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
     private Drawable mHomeDefaultIcon, mHomeCarModeIcon;
     private Drawable mRecentIcon;
-    private Drawable mRecentLandIcon;
+    private Drawable mDockedIcon;
 
     private NavigationBarGestureHelper mGestureHelper;
     private DeadZone mDeadZone;
@@ -97,6 +97,7 @@
     private boolean mLayoutTransitionsEnabled = true;
     private boolean mWakeAndUnlocking;
     private boolean mCarMode = false;
+    private boolean mDockedStackExists;
 
     private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
 
@@ -280,7 +281,7 @@
         mHomeDefaultIcon = ctx.getDrawable(R.drawable.ic_sysbar_home);
 
         mRecentIcon = ctx.getDrawable(R.drawable.ic_sysbar_recent);
-        mRecentLandIcon = mRecentIcon;
+        mDockedIcon = ctx.getDrawable(R.drawable.ic_sysbar_docked);
         getCarModeIcons(ctx);
     }
 
@@ -335,8 +336,7 @@
 
         getBackButton().setImageDrawable(backIcon);
 
-        getRecentsButton().setImageDrawable(
-                mVertical ? mRecentLandIcon : mRecentIcon);
+        updateRecentsIcon();
 
         if (mCarMode) {
             getHomeButton().setImageDrawable(mHomeCarModeIcon);
@@ -507,7 +507,8 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            updateRecentsIcon(exists);
+                            mDockedStackExists = exists;
+                            updateRecentsIcon();
                         }
                     });
                 }
@@ -517,10 +518,8 @@
         }
     }
 
-    private void updateRecentsIcon(boolean dockedStackExists) {
-        getRecentsButton().setImageResource(dockedStackExists
-                ? R.drawable.ic_sysbar_docked
-                : R.drawable.ic_sysbar_recent);
+    private void updateRecentsIcon() {
+        getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
     }
 
     public boolean isVertical() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3aa576f..401d405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -318,7 +318,7 @@
     StatusBarIconController mIconController;
 
     // expanded notifications
-    NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
+    protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     View mExpandedContents;
     TextView mNotificationPanelDebugText;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 3bb141a..bd5bac2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -26,6 +26,7 @@
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -36,15 +37,21 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QuickQSPanel;
+import com.android.systemui.qs.TouchAnimator;
+import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tuner.TunerService;
 
 public class QuickStatusBarHeader extends BaseStatusBarHeader implements
-        NextAlarmController.NextAlarmChangeCallback, View.OnClickListener {
+        NextAlarmChangeCallback, OnClickListener, Listener {
 
     private static final String TAG = "QuickStatusBarHeader";
+
+    private static final float EXPAND_INDICATOR_THRESHOLD = .8f;
+
     private ActivityStarter mActivityStarter;
     private NextAlarmController mNextAlarmController;
     private SettingsButton mSettingsButton;
@@ -58,11 +65,12 @@
     private boolean mExpanded;
     private boolean mAlarmShowing;
 
-    private ViewGroup mExpandedGroup;
     private ViewGroup mDateTimeGroup;
     private ViewGroup mDateTimeAlarmGroup;
     private TextView mEmergencyOnly;
 
+    private ExpandableIndicator mExpandIndicator;
+
     private boolean mListening;
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
@@ -73,8 +81,15 @@
 
     private float mDateTimeTranslation;
     private float mDateTimeAlarmTranslation;
-    private float mExpansionFraction;
     private float mDateScaleFactor;
+    private float mGearTranslation;
+
+    private TouchAnimator mAnimator;
+    private TouchAnimator mSecondHalfAnimator;
+    private TouchAnimator mFirstHalfAnimator;
+    private TouchAnimator mDateSizeAnimator;
+    private TouchAnimator mAlarmTranslation;
+    private float mExpansionAmount;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -89,8 +104,10 @@
         mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
         mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
         mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
+        mDateTimeGroup.setPivotX(0);
+        mDateTimeGroup.setPivotY(0);
 
-        mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
+        mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator);
 
         mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
 
@@ -131,6 +148,8 @@
         FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
         FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
 
+        mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation);
+
         mDateTimeTranslation = mContext.getResources().getDimension(
                 R.dimen.qs_date_anim_translation);
         mDateTimeAlarmTranslation = mContext.getResources().getDimension(
@@ -139,8 +158,31 @@
                 R.dimen.qs_date_collapsed_text_size);
         float dateExpandedSize = mContext.getResources().getDimension(
                 R.dimen.qs_date_text_size);
-        mDateScaleFactor = dateExpandedSize / dateCollapsedSize - 1;
+        mDateScaleFactor = dateExpandedSize / dateCollapsedSize;
         updateDateTimePosition();
+
+        mAnimator = new TouchAnimator.Builder()
+                .addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0)
+                .addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0)
+                .addFloat(mSettingsButton, "rotation", -90, 0)
+                .setListener(this)
+                .build();
+        mSecondHalfAnimator = new TouchAnimator.Builder()
+                .addFloat(mSettingsButton, "rotation", -90, 0)
+                .addFloat(mAlarmStatus, "alpha", 0, 1)
+                .addFloat(mEmergencyOnly, "alpha", 0, 1)
+                .setStartDelay(.5f)
+                .build();
+        mFirstHalfAnimator = new TouchAnimator.Builder()
+                .addFloat(mAlarmStatusCollapsed, "alpha", 1, 0)
+                .addFloat(mHeaderQsPanel, "alpha", 1, 0)
+                .setEndDelay(.5f)
+                .build();
+        mDateSizeAnimator = new TouchAnimator.Builder()
+                .addFloat(mDateTimeGroup, "scaleX", 1, mDateScaleFactor)
+                .addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor)
+                .setStartDelay(.36f)
+                .build();
     }
 
     @Override
@@ -165,45 +207,52 @@
         if (nextAlarm != null) {
             mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
         }
-        mAlarmShowing = nextAlarm != null;
-        updateEverything();
+        if (mAlarmShowing != (nextAlarm != null)) {
+            mAlarmShowing = nextAlarm != null;
+            updateEverything();
+        }
     }
 
     @Override
     public void setExpansion(float headerExpansionFraction) {
-        mExpansionFraction = headerExpansionFraction;
+        mExpansionAmount = headerExpansionFraction;
+        mAnimator.setPosition(headerExpansionFraction);
+        mSecondHalfAnimator.setPosition(headerExpansionFraction);
+        mFirstHalfAnimator.setPosition(headerExpansionFraction);
+        mDateSizeAnimator.setPosition(headerExpansionFraction);
+        mAlarmTranslation.setPosition(headerExpansionFraction);
 
-        mExpandedGroup.setAlpha(headerExpansionFraction);
-        mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
-
-        mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
-        mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
-
-        mAlarmStatus.setAlpha(headerExpansionFraction);
-        mAlarmStatusCollapsed.setAlpha(1 - headerExpansionFraction);
         updateAlarmVisibilities();
 
-        float textScale = headerExpansionFraction * mDateScaleFactor;
-        mDateTimeGroup.setScaleX(1 + textScale);
-        mDateTimeGroup.setScaleY(1 + textScale);
-        mDateTimeGroup.setTranslationX(textScale * mDateTimeGroup.getWidth() / 2);
-        mDateTimeGroup.setTranslationY(textScale * mDateTimeGroup.getHeight() / 2);
-        updateDateTimePosition();
+        mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
+    }
 
-        mEmergencyOnly.setAlpha(headerExpansionFraction);
+    @Override
+    public void onAnimationAtStart() {
+    }
+
+    @Override
+    public void onAnimationAtEnd() {
+        mHeaderQsPanel.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationStarted() {
+        mHeaderQsPanel.setVisibility(View.VISIBLE);
     }
 
     private void updateAlarmVisibilities() {
-        mAlarmStatus.setVisibility(mAlarmShowing && mExpansionFraction > 0
-                ? View.VISIBLE : View.INVISIBLE);
-        mAlarmStatusCollapsed.setVisibility(mAlarmShowing && mExpansionFraction < 1
-                ? View.VISIBLE : View.INVISIBLE);
+        mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
+        mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
     }
 
     private void updateDateTimePosition() {
-        float translation = mAlarmShowing ? mDateTimeAlarmTranslation
-                : mDateTimeTranslation;
-        mDateTimeAlarmGroup.setTranslationY(mExpansionFraction * translation);
+        // This one has its own because we have to rebuild it every time the alarm state changes.
+        mAlarmTranslation = new TouchAnimator.Builder()
+                .addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing
+                        ? mDateTimeAlarmTranslation : mDateTimeTranslation)
+                .build();
+        mAlarmTranslation.setPosition(mExpansionAmount);
     }
 
     public void setListening(boolean listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
new file mode 100644
index 0000000..cc0ffb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.tuner;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.ZenModePanel;
+import com.android.systemui.volume.ZenModePanel.Callback;
+
+public class TunerZenModePanel extends LinearLayout implements OnClickListener {
+    private static final String TAG = "TunerZenModePanel";
+
+    private Callback mCallback;
+    private ZenModePanel mZenModePanel;
+    private View mHeaderSwitch;
+    private int mZenMode;
+    private ZenModeController mController;
+    private View mButtons;
+    private View mMoreSettings;
+    private View mDone;
+    private OnClickListener mDoneListener;
+    private boolean mEditing;
+
+    public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void init(ZenModeController zenModeController) {
+        mController = zenModeController;
+        mHeaderSwitch = findViewById(R.id.tuner_zen_switch);
+        mHeaderSwitch.setVisibility(View.VISIBLE);
+        mHeaderSwitch.setOnClickListener(this);
+        mHeaderSwitch.findViewById(com.android.internal.R.id.up).setVisibility(View.GONE);
+        ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText(
+                R.string.quick_settings_dnd_label);
+        mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
+        mZenModePanel.init(zenModeController);
+        mButtons = findViewById(R.id.tuner_zen_buttons);
+        mMoreSettings = mButtons.findViewById(android.R.id.button2);
+        mMoreSettings.setOnClickListener(this);
+        ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings);
+        mDone = mButtons.findViewById(android.R.id.button1);
+        mDone.setOnClickListener(this);
+        ((TextView) mDone).setText(R.string.quick_settings_done);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mEditing = false;
+    }
+
+    public void setCallback(Callback zenPanelCallback) {
+        mCallback = zenPanelCallback;
+        mZenModePanel.setCallback(zenPanelCallback);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mHeaderSwitch) {
+            mEditing = true;
+            if (mZenMode == Global.ZEN_MODE_OFF) {
+                mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
+                        Global.ZEN_MODE_ALARMS);
+                mController.setZen(mZenMode, null, TAG);
+                postUpdatePanel();
+            } else {
+                mZenMode = Global.ZEN_MODE_OFF;
+                mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
+                postUpdatePanel();
+            }
+        } else if (v == mMoreSettings) {
+            Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            getContext().startActivity(intent);
+        } else if (v == mDone) {
+            mEditing = false;
+            setVisibility(View.GONE);
+            mDoneListener.onClick(v);
+        }
+    }
+
+    public boolean isEditing() {
+        return mEditing;
+    }
+
+    public void setZenState(int zenMode) {
+        mZenMode = zenMode;
+        postUpdatePanel();
+    }
+
+    private void postUpdatePanel() {
+        // The complicated structure from reusing the same ZenPanel has resulted in some
+        // unstableness/flickering from callbacks coming in quickly. To solve this just
+        // post the UI updates a little bit.
+        removeCallbacks(mUpdate);
+        postDelayed(mUpdate, 40);
+    }
+
+    public void setDoneListener(OnClickListener onClickListener) {
+        mDoneListener = onClickListener;
+    }
+
+    private void updatePanel() {
+        boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
+        ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn);
+        mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+        mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+    }
+
+    private final Runnable mUpdate = new Runnable() {
+        @Override
+        public void run() {
+            updatePanel();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 6aae9bd..1810c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -67,6 +67,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerZenModePanel;
 import com.android.systemui.volume.VolumeDialogController.State;
 import com.android.systemui.volume.VolumeDialogController.StreamState;
 
@@ -133,7 +134,7 @@
     private int mLastActiveStream;
 
     private boolean mShowFullZen;
-    private final ZenModePanel mZenPanel;
+    private final TunerZenModePanel mZenPanel;
 
     public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
             ZenModeController zenModeController, Callback callback) {
@@ -225,8 +226,7 @@
         mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
         mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
         mZenFooter.init(zenModeController);
-        mZenPanel = (ZenModePanel) mDialog.findViewById(R.id.zen_mode_panel);
-        mZenPanel.addNoneButton();
+        mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
         mZenPanel.init(zenModeController);
         mZenPanel.setCallback(mZenPanelCallback);
 
@@ -671,7 +671,7 @@
         final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
         final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
                 && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
-                && !mShowFullZen;
+                && !mZenPanel.isEditing();
         if (wasVisible != visible && !visible) {
             prepareForCollapse();
         }
@@ -679,12 +679,21 @@
         mZenFooter.update();
 
         final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
-        final boolean fullVisible = mShowFullZen && (mState.zenMode != Global.ZEN_MODE_OFF
-                || mExpanded);
+        final boolean fullVisible = mShowFullZen && !visible;
         if (fullWasVisible != fullVisible && !fullVisible) {
             prepareForCollapse();
         }
         Util.setVisOrGone(mZenPanel, fullVisible);
+        if (fullVisible) {
+            mZenPanel.setZenState(mState.zenMode);
+            mZenPanel.setDoneListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    prepareForCollapse();
+                    mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
+                }
+            });
+        }
     }
 
     private void updateVolumeRowH(VolumeRow row) {
@@ -978,6 +987,7 @@
         private static final int RESCHEDULE_TIMEOUT = 6;
         private static final int STATE_CHANGED = 7;
         private static final int UPDATE_BOTTOM_MARGIN = 8;
+        private static final int UPDATE_FOOTER = 9;
 
         public H() {
             super(Looper.getMainLooper());
@@ -994,6 +1004,7 @@
                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
                 case STATE_CHANGED: onStateChangedH(mState); break;
                 case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
+                case UPDATE_FOOTER: updateFooterH(); break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 5ca24f7..6976c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -190,12 +190,6 @@
         mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
     }
 
-    public void addNoneButton() {
-        mZenButtons.addButton(R.string.interruption_level_all_twoline,
-                R.string.interruption_level_all,
-                Global.ZEN_MODE_OFF);
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index eaee1d3..a4fc2ec 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -191,7 +191,8 @@
     // 1 : initial release
     // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
     // 3 : introduced "_meta" metadata file; no other format change per se
-    static final int BACKUP_FILE_VERSION = 3;
+    // 4 : added support for new device-encrypted storage locations
+    static final int BACKUP_FILE_VERSION = 4;
     static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
     static final int BACKUP_PW_FILE_VERSION = 2;
     static final String BACKUP_METADATA_FILENAME = "_meta";
@@ -347,13 +348,13 @@
         }
 
         @Override
-        public void onBootPhase(int phase) {
-            if (phase == PHASE_SYSTEM_SERVICES_READY) {
-                sInstance.initialize(UserHandle.USER_SYSTEM);
-            } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+        public void onUnlockUser(int userId) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                sInstance.initialize(userId);
+
                 ContentResolver r = sInstance.mContext.getContentResolver();
-                boolean areEnabled = Settings.Secure.getInt(r,
-                        Settings.Secure.BACKUP_ENABLED, 0) != 0;
+                boolean areEnabled = Settings.Secure.getIntForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, 0, userId) != 0;
                 try {
                     sInstance.setBackupEnabled(areEnabled);
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ca1e371..163b9be 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -660,7 +660,12 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            return readUserDataInternal(accounts, account, key);
+            synchronized (accounts.cacheLock) {
+                if (!accountExistsCacheLocked(accounts, account)) {
+                    return null;
+                }
+                return readUserDataInternalLocked(accounts, account, key);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -1716,44 +1721,58 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            setUserdataInternal(accounts, account, key, value);
+            synchronized (accounts.cacheLock) {
+                if (!accountExistsCacheLocked(accounts, account)) {
+                    return;
+                }
+                setUserdataInternalLocked(accounts, account, key, value);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+    private boolean accountExistsCacheLocked(UserAccounts accounts, Account account) {
+        if (accounts.accountCache.containsKey(account.type)) {
+            for (Account acc : getUserAccountsForCaller().accountCache.get(account.type)) {
+                if (acc.name.equals(account.name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void setUserdataInternalLocked(UserAccounts accounts, Account account, String key,
             String value) {
         if (account == null || key == null) {
             return;
         }
-        synchronized (accounts.cacheLock) {
-            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
-            db.beginTransaction();
-            try {
-                long accountId = getAccountIdLocked(db, account);
-                if (accountId < 0) {
+        final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            long accountId = getAccountIdLocked(db, account);
+            if (accountId < 0) {
+                return;
+            }
+            long extrasId = getExtrasIdLocked(db, accountId, key);
+            if (extrasId < 0) {
+                extrasId = insertExtraLocked(db, accountId, key, value);
+                if (extrasId < 0) {
                     return;
                 }
-                long extrasId = getExtrasIdLocked(db, accountId, key);
-                if (extrasId < 0 ) {
-                    extrasId = insertExtraLocked(db, accountId, key, value);
-                    if (extrasId < 0) {
-                        return;
-                    }
-                } else {
-                    ContentValues values = new ContentValues();
-                    values.put(EXTRAS_VALUE, value);
-                    if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
-                        return;
-                    }
-
+            } else {
+                ContentValues values = new ContentValues();
+                values.put(EXTRAS_VALUE, value);
+                if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+                    return;
                 }
-                writeUserDataIntoCacheLocked(accounts, db, account, key, value);
-                db.setTransactionSuccessful();
-            } finally {
-                db.endTransaction();
+
             }
+            writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
         }
     }
 
@@ -4788,17 +4807,16 @@
         }
     }
 
-    protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
-        synchronized (accounts.cacheLock) {
-            HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
-            if (userDataForAccount == null) {
-                // need to populate the cache for this account
-                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
-                userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
-                accounts.userDataCache.put(account, userDataForAccount);
-            }
-            return userDataForAccount.get(key);
+    protected String readUserDataInternalLocked(
+            UserAccounts accounts, Account account, String key) {
+        HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+        if (userDataForAccount == null) {
+            // need to populate the cache for this account
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+            userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+            accounts.userDataCache.put(account, userDataForAccount);
         }
+        return userDataForAccount.get(key);
     }
 
     protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index 479b065..98fe770 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -17,6 +17,7 @@
 package com.android.server.net;
 
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_REMOVED;
 import static android.net.TrafficStats.UID_TETHERING;
 
@@ -48,6 +49,7 @@
     @IntDef({
             Level.DEFAULT,
             Level.USER,
+            Level.DEVICESUMMARY,
             Level.DEVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -147,6 +149,12 @@
                 // Device-level access - can access usage for any uid.
                 return true;
             case NetworkStatsAccess.Level.DEVICESUMMARY:
+                // Can access usage for any app running in the same user, along
+                // with some special uids (system, removed, or tethering) and
+                // anonymized uids
+                return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
+                        || uid == UID_TETHERING || uid == UID_ALL
+                        || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
             case NetworkStatsAccess.Level.USER:
                 // User-level access - can access usage for any app running in the same user, along
                 // with some special uids (system, removed, or tethering).
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index eec7d93..d986e94b 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -135,7 +135,11 @@
     }
 
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
-        final int callerUid = Binder.getCallingUid();
+        return getRelevantUids(accessLevel, Binder.getCallingUid());
+    }
+
+    public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
+                final int callerUid) {
         IntArray uids = new IntArray();
         for (int i = 0; i < mStats.size(); i++) {
             final Key key = mStats.keyAt(i);
@@ -169,7 +173,17 @@
     public NetworkStatsHistory getHistory(
             NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
             @NetworkStatsAccess.Level int accessLevel) {
-        final int callerUid = Binder.getCallingUid();
+        return getHistory(template, uid, set, tag, fields, start, end, accessLevel,
+                Binder.getCallingUid());
+    }
+
+    /**
+     * Combine all {@link NetworkStatsHistory} in this collection which match
+     * the requested parameters.
+     */
+    public NetworkStatsHistory getHistory(
+            NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
+            @NetworkStatsAccess.Level int accessLevel, int callerUid) {
         if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
             throw new SecurityException("Network stats history of uid " + uid
                     + " is forbidden for caller " + callerUid);
@@ -198,6 +212,15 @@
      */
     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
             @NetworkStatsAccess.Level int accessLevel) {
+        return getSummary(template, start, end, accessLevel, Binder.getCallingUid());
+    }
+
+    /**
+     * Summarize all {@link NetworkStatsHistory} in this collection which match
+     * the requested parameters.
+     */
+    public NetworkStats getSummary(NetworkTemplate template, long start, long end,
+            @NetworkStatsAccess.Level int accessLevel, int callerUid) {
         final long now = System.currentTimeMillis();
 
         final NetworkStats stats = new NetworkStats(end - start, 24);
@@ -207,7 +230,6 @@
         final NetworkStats.Entry entry = new NetworkStats.Entry();
         NetworkStatsHistory.Entry historyEntry = null;
 
-        final int callerUid = Binder.getCallingUid();
         for (int i = 0; i < mStats.size(); i++) {
             final Key key = mStats.keyAt(i);
             if (templateMatches(template, key.ident)
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java
new file mode 100644
index 0000000..2f55562
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java
@@ -0,0 +1,493 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net;
+
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.DataUsageRequest;
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.SparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.VpnInfo;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages observers of {@link NetworkStats}. Allows observers to be notified when
+ * data usage has been reported in {@link NetworkStatsService}. An observer can set
+ * a threshold of how much data it cares about to be notified.
+ */
+class NetworkStatsObservers {
+    private static final String TAG = "NetworkStatsObservers";
+    private static final boolean LOGV = true;
+
+    private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
+
+    private static final int MSG_REGISTER = 1;
+    private static final int MSG_UNREGISTER = 2;
+    private static final int MSG_UPDATE_STATS = 3;
+
+    // All access to this map must be done from the handler thread.
+    // indexed by DataUsageRequest#requestId
+    private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
+
+    // Sequence number of DataUsageRequests
+    private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
+
+    // Lazily instantiated when an observer is registered.
+    private Handler mHandler;
+
+    /**
+     * Creates a wrapper that contains the caller context and a normalized request.
+     * The request should be returned to the caller app, and the wrapper should be sent to this
+     * object through #addObserver by the service handler.
+     *
+     * <p>It will register the observer asynchronously, so it is safe to call from any thread.
+     *
+     * @return the normalized request wrapped within {@link RequestInfo}.
+     */
+    public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
+                IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+        checkVisibilityUids(callingUid, accessLevel, inputRequest.uids);
+
+        DataUsageRequest request = buildRequest(inputRequest);
+        RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
+                accessLevel);
+
+        if (LOGV) Slog.v(TAG, "Registering observer for " + request);
+        getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
+        return request;
+    }
+
+    /**
+     * Unregister a data usage observer.
+     *
+     * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
+     */
+    public void unregister(DataUsageRequest request, int callingUid) {
+        getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
+                request));
+    }
+
+    /**
+     * Updates data usage statistics of registered observers and notifies if limits are reached.
+     *
+     * <p>It will update stats asynchronously, so it is safe to call from any thread.
+     */
+    public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
+                ArrayMap<String, NetworkIdentitySet> activeIfaces,
+                ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
+                VpnInfo[] vpnArray, long currentTime) {
+        StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
+                activeUidIfaces, vpnArray, currentTime);
+        getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            synchronized (this) {
+                if (mHandler == null) {
+                    if (LOGV) Slog.v(TAG, "Creating handler");
+                    mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
+                }
+            }
+        }
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    protected Looper getHandlerLooperLocked() {
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        return handlerThread.getLooper();
+    }
+
+    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REGISTER: {
+                    handleRegister((RequestInfo) msg.obj);
+                    return true;
+                }
+                case MSG_UNREGISTER: {
+                    handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
+                    return true;
+                }
+                case MSG_UPDATE_STATS: {
+                    handleUpdateStats((StatsContext) msg.obj);
+                    return true;
+                }
+                default: {
+                    return false;
+                }
+            }
+        }
+    };
+
+    /**
+     * Adds a {@link RequestInfo} as an observer.
+     * Should only be called from the handler thread otherwise there will be a race condition
+     * on mDataUsageRequests.
+     */
+    private void handleRegister(RequestInfo requestInfo) {
+        mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
+    }
+
+    /**
+     * Removes a {@link DataUsageRequest} if the calling uid is authorized.
+     * Should only be called from the handler thread otherwise there will be a race condition
+     * on mDataUsageRequests.
+     */
+    private void handleUnregister(DataUsageRequest request, int callingUid) {
+        RequestInfo requestInfo;
+        requestInfo = mDataUsageRequests.get(request.requestId);
+        if (requestInfo == null) {
+            if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
+            return;
+        }
+        if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
+            Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
+            return;
+        }
+
+        if (LOGV) Slog.v(TAG, "Unregistering " + request);
+        mDataUsageRequests.remove(request.requestId);
+        requestInfo.unlinkDeathRecipient();
+        requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
+    }
+
+    private void handleUpdateStats(StatsContext statsContext) {
+        if (mDataUsageRequests.size() == 0) {
+            if (LOGV) Slog.v(TAG, "No registered listeners of data usage");
+            return;
+        }
+
+        if (LOGV) Slog.v(TAG, "Checking if any registered observer needs to be notified");
+        for (int i = 0; i < mDataUsageRequests.size(); i++) {
+            RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
+            requestInfo.updateStats(statsContext);
+        }
+    }
+
+    private DataUsageRequest buildRequest(DataUsageRequest request) {
+        // Cap the minimum threshold to a safe default to avoid too many callbacks
+        long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
+        if (thresholdInBytes < request.thresholdInBytes) {
+            Slog.w(TAG, "Threshold was too low for " + request
+                    + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
+        }
+        return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
+                request.templates, request.uids, thresholdInBytes);
+    }
+
+    private RequestInfo buildRequestInfo(DataUsageRequest request,
+                Messenger messenger, IBinder binder, int callingUid,
+                @NetworkStatsAccess.Level int accessLevel) {
+        if (accessLevel <= NetworkStatsAccess.Level.USER
+                || request.uids != null && request.uids.length > 0) {
+            return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
+                    accessLevel);
+        } else {
+            // Safety check in case a new access level is added and we forgot to update this
+            checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
+            return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
+                    accessLevel);
+        }
+    }
+
+    private void checkVisibilityUids(int callingUid, @NetworkStatsAccess.Level int accessLevel,
+                int[] uids) {
+        if (uids == null) {
+            return;
+        }
+        for (int i = 0; i < uids.length; i++) {
+            if (!NetworkStatsAccess.isAccessibleToUser(uids[i], callingUid, accessLevel)) {
+                throw new SecurityException("Caller " + callingUid + " cannot monitor network stats"
+                        + " for uid " + uids[i] + " with accessLevel " + accessLevel);
+            }
+        }
+    }
+
+    /**
+     * Tracks information relevant to a data usage observer.
+     * It will notice when the calling process dies so we can self-expire.
+     */
+    private abstract static class RequestInfo implements IBinder.DeathRecipient {
+        private final NetworkStatsObservers mStatsObserver;
+        protected final DataUsageRequest mRequest;
+        private final Messenger mMessenger;
+        private final IBinder mBinder;
+        protected final int mCallingUid;
+        protected final @NetworkStatsAccess.Level int mAccessLevel;
+        protected NetworkStatsRecorder mRecorder;
+        protected NetworkStatsCollection mCollection;
+
+        RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+                    Messenger messenger, IBinder binder, int callingUid,
+                    @NetworkStatsAccess.Level int accessLevel) {
+            mStatsObserver = statsObserver;
+            mRequest = request;
+            mMessenger = messenger;
+            mBinder = binder;
+            mCallingUid = callingUid;
+            mAccessLevel = accessLevel;
+
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
+                    + mRequest + ", " + mBinder + ")");
+            mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
+            callCallback(NetworkStatsManager.CALLBACK_RELEASED);
+        }
+
+        @Override
+        public String toString() {
+            return "RequestInfo from uid:" + mCallingUid
+                    + " for " + mRequest + " accessLevel:" + mAccessLevel;
+        }
+
+        private void unlinkDeathRecipient() {
+            if (mBinder != null) {
+                mBinder.unlinkToDeath(this, 0);
+            }
+        }
+
+        /**
+         * Update stats given the samples and interface to identity mappings.
+         */
+        private void updateStats(StatsContext statsContext) {
+            if (mRecorder == null) {
+                // First run; establish baseline stats
+                resetRecorder();
+                recordSample(statsContext);
+                return;
+            }
+            recordSample(statsContext);
+
+            if (checkStats()) {
+                resetRecorder();
+                callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
+            }
+        }
+
+        private void callCallback(int callbackType) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
+            Message msg = Message.obtain();
+            msg.what = callbackType;
+            msg.setData(bundle);
+            try {
+                if (LOGV) {
+                    Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+                            + " for " + mRequest);
+                }
+                mMessenger.send(msg);
+            } catch (RemoteException e) {
+                // May occur naturally in the race of binder death.
+                Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
+            }
+        }
+
+        private void resetRecorder() {
+            mRecorder = new NetworkStatsRecorder();
+            mCollection = mRecorder.getSinceBoot();
+        }
+
+        protected abstract boolean checkStats();
+
+        protected abstract void recordSample(StatsContext statsContext);
+
+        private String callbackTypeToName(int callbackType) {
+            switch (callbackType) {
+                case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+                    return "LIMIT_REACHED";
+                case NetworkStatsManager.CALLBACK_RELEASED:
+                    return "RELEASED";
+                default:
+                    return "UNKNOWN";
+            }
+        }
+    }
+
+    private static class NetworkUsageRequestInfo extends RequestInfo {
+        NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+                    Messenger messenger, IBinder binder, int callingUid,
+                    @NetworkStatsAccess.Level int accessLevel) {
+            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+        }
+
+        @Override
+        protected boolean checkStats() {
+            for (int i = 0; i < mRequest.templates.length; i++) {
+                long bytesSoFar = getTotalBytesForNetwork(mRequest.templates[i]);
+                if (LOGV) {
+                    Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+                            + mRequest.templates[i]);
+                }
+                if (bytesSoFar > mRequest.thresholdInBytes) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected void recordSample(StatsContext statsContext) {
+            // Recorder does not need to be locked in this context since only the handler
+            // thread will update it
+            mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
+                    statsContext.mVpnArray, statsContext.mCurrentTime);
+        }
+
+        /**
+         * Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
+         * over all buckets, which in this case should be only one since we built it big enough
+         * that it will outlive the caller. If it doesn't, then there will be multiple buckets.
+         */
+        private long getTotalBytesForNetwork(NetworkTemplate template) {
+            NetworkStats stats = mCollection.getSummary(template,
+                    Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
+                    mAccessLevel, mCallingUid);
+            if (LOGV) {
+                Slog.v(TAG, "Netstats for " + template + ": " + stats);
+            }
+            return stats.getTotalBytes();
+        }
+    }
+
+    private static class UserUsageRequestInfo extends RequestInfo {
+        UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+                    Messenger messenger, IBinder binder, int callingUid,
+                    @NetworkStatsAccess.Level int accessLevel) {
+            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+        }
+
+        @Override
+        protected boolean checkStats() {
+            int[] uidsToMonitor = getUidsToMonitor();
+
+            for (int i = 0; i < mRequest.templates.length; i++) {
+                for (int j = 0; j < uidsToMonitor.length; j++) {
+                    long bytesSoFar = getTotalBytesForNetworkUid(mRequest.templates[i],
+                            uidsToMonitor[j]);
+
+                    if (LOGV) {
+                        Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+                                + mRequest.templates[i] + " for uid=" + uidsToMonitor[j]);
+                    }
+                    if (bytesSoFar > mRequest.thresholdInBytes) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected void recordSample(StatsContext statsContext) {
+            // Recorder does not need to be locked in this context since only the handler
+            // thread will update it
+            mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
+                    statsContext.mVpnArray, statsContext.mCurrentTime);
+        }
+
+        /**
+         * Reads all stats matching the given template and uid. Ther history will likely only
+         * contain one bucket per ident since we build it big enough that it will outlive the
+         * caller lifetime.
+         */
+        private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
+            try {
+                NetworkStatsHistory history = mCollection.getHistory(template, uid,
+                        NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
+                        NetworkStatsHistory.FIELD_ALL,
+                        Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
+                        mAccessLevel, mCallingUid);
+                return history.getTotalBytes();
+            } catch (SecurityException e) {
+                if (LOGV) {
+                    Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+                            + uid);
+                }
+                return 0;
+            }
+        }
+
+        private int[] getUidsToMonitor() {
+            if (mRequest.uids == null || mRequest.uids.length == 0) {
+                return mCollection.getRelevantUids(mAccessLevel, mCallingUid);
+            }
+            // Pick only uids from the request that are currently accessible to the user
+            IntArray accessibleUids = new IntArray(mRequest.uids.length);
+            for (int i = 0; i < mRequest.uids.length; i++) {
+                int uid = mRequest.uids[i];
+                if (NetworkStatsAccess.isAccessibleToUser(uid, mCallingUid, mAccessLevel)) {
+                    accessibleUids.add(uid);
+                }
+            }
+            return accessibleUids.toArray();
+        }
+    }
+
+    private static class StatsContext {
+        NetworkStats mXtSnapshot;
+        NetworkStats mUidSnapshot;
+        ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
+        ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
+        VpnInfo[] mVpnArray;
+        long mCurrentTime;
+
+        StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
+                ArrayMap<String, NetworkIdentitySet> activeIfaces,
+                ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
+                VpnInfo[] vpnArray, long currentTime) {
+            mXtSnapshot = xtSnapshot;
+            mUidSnapshot = uidSnapshot;
+            mActiveIfaces = activeIfaces;
+            mActiveUidIfaces = activeUidIfaces;
+            mVpnArray = vpnArray;
+            mCurrentTime = currentTime;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index c091960..04dc917 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.net.NetworkStats;
@@ -54,7 +55,7 @@
  * Logic to record deltas between periodic {@link NetworkStats} snapshots into
  * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
  * Keeps pending changes in memory until they pass a specific threshold, in
- * bytes. Uses {@link FileRotator} for persistence logic.
+ * bytes. Uses {@link FileRotator} for persistence logic if present.
  * <p>
  * Not inherently thread safe.
  */
@@ -86,6 +87,29 @@
 
     private WeakReference<NetworkStatsCollection> mComplete;
 
+    /**
+     * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
+     */
+    public NetworkStatsRecorder() {
+        mRotator = null;
+        mObserver = null;
+        mDropBox = null;
+        mCookie = null;
+
+        // set the bucket big enough to have all data in one bucket, but allow some
+        // slack to avoid overflow
+        mBucketDuration = YEAR_IN_MILLIS;
+        mOnlyTags = false;
+
+        mPending = null;
+        mSinceBoot = new NetworkStatsCollection(mBucketDuration);
+
+        mPendingRewriter = null;
+    }
+
+    /**
+     * Persisted recorder.
+     */
     public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
             DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
         mRotator = checkNotNull(rotator, "missing FileRotator");
@@ -110,9 +134,15 @@
 
     public void resetLocked() {
         mLastSnapshot = null;
-        mPending.reset();
-        mSinceBoot.reset();
-        mComplete.clear();
+        if (mPending != null) {
+            mPending.reset();
+        }
+        if (mSinceBoot != null) {
+            mSinceBoot.reset();
+        }
+        if (mComplete != null) {
+            mComplete.clear();
+        }
     }
 
     public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
@@ -120,6 +150,10 @@
                 NetworkStatsAccess.Level.DEVICE).getTotal(null);
     }
 
+    public NetworkStatsCollection getSinceBoot() {
+        return mSinceBoot;
+    }
+
     /**
      * Load complete history represented by {@link FileRotator}. Caches
      * internally as a {@link WeakReference}, and updated with future
@@ -127,6 +161,7 @@
      * as reference is valid.
      */
     public NetworkStatsCollection getOrLoadCompleteLocked() {
+        checkNotNull(mRotator, "missing FileRotator");
         NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
         if (res == null) {
             res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
@@ -136,6 +171,7 @@
     }
 
     public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
+        checkNotNull(mRotator, "missing FileRotator");
         NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
         if (res == null) {
             res = loadLocked(start, end);
@@ -205,7 +241,9 @@
 
             // only record tag data when requested
             if ((entry.tag == TAG_NONE) != mOnlyTags) {
-                mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+                if (mPending != null) {
+                    mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+                }
 
                 // also record against boot stats when present
                 if (mSinceBoot != null) {
@@ -231,6 +269,7 @@
      * {@link #mPersistThresholdBytes}.
      */
     public void maybePersistLocked(long currentTimeMillis) {
+        checkNotNull(mRotator, "missing FileRotator");
         final long pendingBytes = mPending.getTotalBytes();
         if (pendingBytes >= mPersistThresholdBytes) {
             forcePersistLocked(currentTimeMillis);
@@ -243,6 +282,7 @@
      * Force persisting any pending deltas.
      */
     public void forcePersistLocked(long currentTimeMillis) {
+        checkNotNull(mRotator, "missing FileRotator");
         if (mPending.isDirty()) {
             if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
             try {
@@ -264,20 +304,26 @@
      * to {@link TrafficStats#UID_REMOVED}.
      */
     public void removeUidsLocked(int[] uids) {
-        try {
-            // Rewrite all persisted data to migrate UID stats
-            mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
-        } catch (IOException e) {
-            Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
-            recoverFromWtf();
-        } catch (OutOfMemoryError e) {
-            Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
-            recoverFromWtf();
+        if (mRotator != null) {
+            try {
+                // Rewrite all persisted data to migrate UID stats
+                mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
+            } catch (IOException e) {
+                Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+                recoverFromWtf();
+            } catch (OutOfMemoryError e) {
+                Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+                recoverFromWtf();
+            }
         }
 
         // Remove any pending stats
-        mPending.removeUids(uids);
-        mSinceBoot.removeUids(uids);
+        if (mPending != null) {
+            mPending.removeUids(uids);
+        }
+        if (mSinceBoot != null) {
+            mSinceBoot.removeUids(uids);
+        }
 
         // Clear UID from current stats snapshot
         if (mLastSnapshot != null) {
@@ -361,6 +407,8 @@
     }
 
     public void importLegacyNetworkLocked(File file) throws IOException {
+        checkNotNull(mRotator, "missing FileRotator");
+
         // legacy file still exists; start empty to avoid double importing
         mRotator.deleteAll();
 
@@ -379,6 +427,8 @@
     }
 
     public void importLegacyUidLocked(File file) throws IOException {
+        checkNotNull(mRotator, "missing FileRotator");
+
         // legacy file still exists; start empty to avoid double importing
         mRotator.deleteAll();
 
@@ -397,7 +447,9 @@
     }
 
     public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
-        pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+        if (mPending != null) {
+            pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+        }
         if (fullHistory) {
             pw.println("Complete history:");
             getOrLoadCompleteLocked().dump(pw);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 3aeceef..2c2e9b9 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -57,6 +57,7 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
 import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -72,6 +73,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.DataUsageRequest;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsService;
@@ -90,8 +92,10 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
+import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -152,6 +156,7 @@
     private final TrustedTime mTime;
     private final TelephonyManager mTeleManager;
     private final NetworkStatsSettings mSettings;
+    private final NetworkStatsObservers mStatsObservers;
 
     private final File mSystemDir;
     private final File mBaseDir;
@@ -233,43 +238,65 @@
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
 
-    private final Handler mHandler;
+    /** Must be set in factory by calling #setHandler. */
+    private Handler mHandler;
+    private Handler.Callback mHandlerCallback;
 
     private boolean mSystemReady;
     private long mPersistThreshold = 2 * MB_IN_BYTES;
     private long mGlobalAlertBytes;
 
-    public NetworkStatsService(
-            Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
-        this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
-                getDefaultSystemDir(), new DefaultNetworkStatsSettings(context));
-    }
-
     private static File getDefaultSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
 
-    public NetworkStatsService(Context context, INetworkManagementService networkManager,
-            IAlarmManager alarmManager, TrustedTime time, File systemDir,
-            NetworkStatsSettings settings) {
+    private static File getDefaultBaseDir() {
+        File baseDir = new File(getDefaultSystemDir(), "netstats");
+        baseDir.mkdirs();
+        return baseDir;
+    }
+
+    public static NetworkStatsService create(Context context,
+                INetworkManagementService networkManager) {
+        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
+                wakeLock, NtpTrustedTime.getInstance(context), TelephonyManager.getDefault(),
+                new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
+                getDefaultSystemDir(), getDefaultBaseDir());
+
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        Handler.Callback callback = new HandlerCallback(service);
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper(), callback);
+        service.setHandler(handler, callback);
+        return service;
+    }
+
+    @VisibleForTesting
+    NetworkStatsService(Context context, INetworkManagementService networkManager,
+            AlarmManager alarmManager, PowerManager.WakeLock wakeLock, TrustedTime time,
+            TelephonyManager teleManager, NetworkStatsSettings settings,
+            NetworkStatsObservers statsObservers, File systemDir, File baseDir) {
         mContext = checkNotNull(context, "missing Context");
         mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+        mAlarmManager = checkNotNull(alarmManager, "missing AlarmManager");
         mTime = checkNotNull(time, "missing TrustedTime");
-        mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager");
         mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mTeleManager = checkNotNull(teleManager, "missing TelephonyManager");
+        mWakeLock = checkNotNull(wakeLock, "missing WakeLock");
+        mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
+        mSystemDir = checkNotNull(systemDir, "missing systemDir");
+        mBaseDir = checkNotNull(baseDir, "missing baseDir");
+    }
 
-        final PowerManager powerManager = (PowerManager) context.getSystemService(
-                Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
-        HandlerThread thread = new HandlerThread(TAG);
-        thread.start();
-        mHandler = new Handler(thread.getLooper(), mHandlerCallback);
-
-        mSystemDir = checkNotNull(systemDir);
-        mBaseDir = new File(systemDir, "netstats");
-        mBaseDir.mkdirs();
+    @VisibleForTesting
+    void setHandler(Handler handler, Handler.Callback callback) {
+        mHandler = handler;
+        mHandlerCallback = callback;
     }
 
     public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -733,6 +760,46 @@
         registerGlobalAlert();
     }
 
+    @Override
+    public DataUsageRequest registerDataUsageCallback(String callingPackage,
+                DataUsageRequest request, Messenger messenger, IBinder binder) {
+        checkNotNull(callingPackage, "calling package is null");
+        checkNotNull(request, "DataUsageRequest is null");
+        checkNotNull(request.templates, "NetworkTemplate is null");
+        checkArgument(request.templates.length > 0);
+        checkNotNull(messenger, "messenger is null");
+        checkNotNull(binder, "binder is null");
+
+        int callingUid = Binder.getCallingUid();
+        @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
+        DataUsageRequest normalizedRequest;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            normalizedRequest = mStatsObservers.register(request, messenger, binder,
+                    callingUid, accessLevel);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        // Create baseline stats
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL, FLAG_PERSIST_ALL));
+
+        return normalizedRequest;
+   }
+
+    @Override
+    public void unregisterDataUsageRequest(DataUsageRequest request) {
+        checkNotNull(request, "DataUsageRequest is null");
+
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mStatsObservers.unregister(request, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
      * reflect current {@link #mPersistThreshold} value. Always defers to
@@ -945,6 +1012,11 @@
         mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, null, currentTime);
         mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
         mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+
+        // We need to make copies of member fields that are sent to the observer to avoid
+        // a race condition between the service handler thread and the observer's
+        mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
+                new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
     }
 
     /**
@@ -1243,21 +1315,28 @@
         }
     }
 
-    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+    @VisibleForTesting
+    static class HandlerCallback implements Handler.Callback {
+        private final NetworkStatsService mService;
+
+        HandlerCallback(NetworkStatsService service) {
+            this.mService = service;
+        }
+
         @Override
         public boolean handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_PERFORM_POLL: {
                     final int flags = msg.arg1;
-                    performPoll(flags);
+                    mService.performPoll(flags);
                     return true;
                 }
                 case MSG_UPDATE_IFACES: {
-                    updateIfaces();
+                    mService.updateIfaces();
                     return true;
                 }
                 case MSG_REGISTER_GLOBAL_ALERT: {
-                    registerGlobalAlert();
+                    mService.registerGlobalAlert();
                     return true;
                 }
                 default: {
@@ -1265,7 +1344,7 @@
                 }
             }
         }
-    };
+    }
 
     private void assertBandwidthControlEnabled() {
         if (!isBandwidthControlEnabled()) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0cf9328..ac972a9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -760,7 +760,7 @@
 
                 traceBeginAndSlog("StartNetworkStatsService");
                 try {
-                    networkStats = new NetworkStatsService(context, networkManagement, alarm);
+                    networkStats = NetworkStatsService.create(context, networkManagement);
                     ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
                 } catch (Throwable e) {
                     reportWtf("starting NetworkStats Service", e);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
new file mode 100644
index 0000000..b9e9aa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
@@ -0,0 +1,634 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.ROAMING_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.DataUsageRequest;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Process;
+
+import android.os.ConditionVariable;
+import android.os.Looper;
+import android.os.Messenger;
+import android.os.Message;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+
+import com.android.internal.net.VpnInfo;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsServiceTest.IdleableHandlerThread;
+import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NetworkStatsObservers}.
+ */
+public class NetworkStatsObserversTest extends TestCase {
+    private static final String TEST_IFACE = "test0";
+    private static final String TEST_IFACE2 = "test1";
+    private static final long TEST_START = 1194220800000L;
+
+    private static final String IMSI_1 = "310004";
+    private static final String IMSI_2 = "310260";
+    private static final String TEST_SSID = "AndroidAP";
+
+    private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+    private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+    private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+    private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
+    private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
+    private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
+    private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+
+    private static final long WAIT_TIMEOUT = 500;  // 1/2 sec
+    private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
+    private static final long BASE_BYTES = 7 * MB_IN_BYTES;
+    private static final int INVALID_TYPE = -1;
+
+    private static final int[] NO_UIDS = null;
+    private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
+
+    private long mElapsedRealtime;
+
+    private IdleableHandlerThread mObserverHandlerThread;
+    private Handler mObserverNoopHandler;
+
+    private LatchedHandler mHandler;
+    private ConditionVariable mCv;
+
+    private NetworkStatsObservers mStatsObservers;
+    private Messenger mMessenger;
+    private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
+    private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
+
+    @Mock private IBinder mockBinder;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mObserverHandlerThread.start();
+        final Looper observerLooper = mObserverHandlerThread.getLooper();
+        mStatsObservers = new NetworkStatsObservers() {
+            @Override
+            protected Looper getHandlerLooperLocked() {
+                return observerLooper;
+            }
+        };
+
+        mCv = new ConditionVariable();
+        mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
+        mMessenger = new Messenger(mHandler);
+
+        mActiveIfaces = new ArrayMap<>();
+        mActiveUidIfaces = new ArrayMap<>();
+    }
+
+    public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
+        long thresholdTooLowBytes = 1L;
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, thresholdTooLowBytes);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testRegister_highThreshold_accepted() throws Exception {
+        long highThresholdBytes = 2 * THRESHOLD_BYTES;
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, highThresholdBytes);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(highThresholdBytes, request.thresholdInBytes);
+    }
+
+    public void testRegister_twoRequests_twoIds() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request1.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request1.templates));
+        assertNull(request1.uids);
+        assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
+
+        DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request2.requestId > request1.requestId);
+        assertTrue(Arrays.deepEquals(templates, request2.templates));
+        assertNull(request2.uids);
+        assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
+    }
+
+    public void testRegister_defaultAccess_otherUids_securityException() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        try {
+            mStatsObservers.register(inputRequest, mMessenger, mockBinder, UID_RED,
+                    NetworkStatsAccess.Level.DEFAULT);
+            fail("Should have denied access");
+        } catch (SecurityException expected) {}
+    }
+
+    public void testRegister_userAccess_otherUidsSameUser()
+            throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testRegister_defaultAccess_sameUid() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testUnregister_unknownRequest_noop() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest unknownRequest = new DataUsageRequest(
+                123456 /* id */, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        mStatsObservers.unregister(unknownRequest, UID_RED);
+    }
+
+    public void testUnregister_knownRequest_releasesCaller() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+        Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+        mStatsObservers.unregister(request, Process.SYSTEM_UID);
+        waitForObserverToIdle();
+
+        Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+    }
+
+    public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+        Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+        mStatsObservers.unregister(request, UID_BLUE);
+        waitForObserverToIdle();
+
+        Mockito.verifyZeroInteractions(mockBinder);
+    }
+
+    public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        mCv.block(WAIT_TIMEOUT);
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdNetwork_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
+                        BASE_BYTES + THRESHOLD_BYTES, 22L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdMultipleNetwork_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEVICESUMMARY);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet1 = new NetworkIdentitySet();
+        identSet1.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveIfaces.put(TEST_IFACE, identSet1);
+
+        NetworkIdentitySet identSet2 = new NetworkIdentitySet();
+        identSet2.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_2, null /* networkId */, false /* roaming */));
+        mActiveIfaces.put(TEST_IFACE2, identSet2);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L)
+                .addIfaceValues(TEST_IFACE2, BASE_BYTES + 1234L, 18L, BASE_BYTES, 12L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta - traffic on IMSI2
+        xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L)
+                .addIfaceValues(TEST_IFACE2, BASE_BYTES + THRESHOLD_BYTES, 22L,
+                        BASE_BYTES + THRESHOLD_BYTES, 24L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdUid_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids,request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        uidSnapshot = new NetworkStats(TEST_START+ 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_defaultAccess_noUid_notifiesSameUid() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        uidSnapshot = new NetworkStats(TEST_START+ 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_defaultAccess_noUid_usageOtherUid_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        uidSnapshot = new NetworkStats(TEST_START+ 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_BLUE, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        uidSnapshot = new NetworkStats(TEST_START+ 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        uidSnapshot = new NetworkStats(TEST_START+ 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    private void waitForObserverToIdle() {
+        // Send dummy message to make sure that any previous message has been handled
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
rename to services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
index 8cbd32d..4f6c7b9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.net;
 
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.EXTRA_UID;
@@ -43,6 +43,7 @@
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
 import static org.easymock.EasyMock.anyInt;
 import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.eq;
@@ -54,7 +55,10 @@
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
 import android.content.Intent;
+import android.net.DataUsageRequest;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsSession;
@@ -65,7 +69,17 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.INetworkManagementService;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Messenger;
+import android.os.MessageQueue;
+import android.os.MessageQueue.IdleHandler;
+import android.os.Message;
+import android.os.PowerManager;
 import android.os.WorkSource;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
@@ -74,6 +88,7 @@
 import android.util.TrustedTime;
 
 import com.android.internal.net.VpnInfo;
+import com.android.server.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -85,6 +100,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -113,16 +129,20 @@
     private static final int UID_BLUE = 1002;
     private static final int UID_GREEN = 1003;
 
+    private static final long WAIT_TIMEOUT = 2 * 1000;  // 2 secs
+    private static final int INVALID_TYPE = -1;
+
     private long mElapsedRealtime;
 
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
 
     private INetworkManagementService mNetManager;
-    private IAlarmManager mAlarmManager;
     private TrustedTime mTime;
     private NetworkStatsSettings mSettings;
     private IConnectivityManager mConnManager;
+    private IdleableHandlerThread mHandlerThread;
+    private Handler mHandler;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -139,13 +159,28 @@
         }
 
         mNetManager = createMock(INetworkManagementService.class);
-        mAlarmManager = createMock(IAlarmManager.class);
+
+        // TODO: Mock AlarmManager when migrating this test to Mockito.
+        AlarmManager alarmManager = (AlarmManager) mServiceContext
+                .getSystemService(Context.ALARM_SERVICE);
         mTime = createMock(TrustedTime.class);
         mSettings = createMock(NetworkStatsSettings.class);
         mConnManager = createMock(IConnectivityManager.class);
 
+        PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
+                Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
         mService = new NetworkStatsService(
-                mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir, mSettings);
+                mServiceContext, mNetManager, alarmManager, wakeLock, mTime,
+                TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
+                mStatsDir, getBaseDir(mStatsDir));
+        mHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mHandlerThread.start();
+        Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
+        mHandler = new Handler(mHandlerThread.getLooper(), callback);
+        mService.setHandler(mHandler, callback);
         mService.bindConnectivityManager(mConnManager);
 
         mElapsedRealtime = 0L;
@@ -178,7 +213,6 @@
         mStatsDir = null;
 
         mNetManager = null;
-        mAlarmManager = null;
         mTime = null;
         mSettings = null;
         mConnManager = null;
@@ -217,7 +251,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
@@ -234,7 +268,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
@@ -282,7 +316,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
@@ -362,7 +396,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
@@ -380,7 +414,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify identical stats, but spread across 4 buckets now
         history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
@@ -420,7 +454,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -446,7 +480,7 @@
 
         replay();
         mService.forceUpdateIfaces();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
         verifyAndReset();
 
         // create traffic on second network
@@ -465,7 +499,7 @@
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify original history still intact
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -511,7 +545,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
@@ -578,7 +612,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
@@ -598,7 +632,7 @@
 
         replay();
         mService.forceUpdateIfaces();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
         verifyAndReset();
 
         // create traffic on second network
@@ -616,7 +650,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify that ALL_MOBILE template combines both
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
@@ -652,7 +686,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
@@ -671,7 +705,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // first verify entire history present
         NetworkStats stats = mSession.getSummaryForAllUid(
@@ -722,7 +756,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
@@ -744,7 +778,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // test that we combined correctly
         assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
@@ -795,7 +829,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
@@ -843,7 +877,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -853,6 +887,285 @@
 
     }
 
+    public void testRegisterDataUsageCallback_network() throws Exception {
+        // pretend that wifi network comes online; service should ask about full
+        // network state, and poll any existing interfaces before updating.
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
+
+        replay();
+        mService.forceUpdateIfaces();
+
+        // verify service has empty history for wifi
+        assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+        verifyAndReset();
+
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 1L;  // very small; should be overriden by framework
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, null /* uids */, thresholdInBytes);
+
+        // Create a messenger that waits for callback activity
+        ConditionVariable cv = new ConditionVariable(false);
+        LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
+        Messenger messenger = new Messenger(latchedHandler);
+
+        // Allow binder to connect
+        IBinder mockBinder = createMock(IBinder.class);
+        mockBinder.linkToDeath((IBinder.DeathRecipient) anyObject(), anyInt());
+        EasyMock.replay(mockBinder);
+
+        // Force poll
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        replay();
+
+        // Register and verify request and that binder was called
+        DataUsageRequest request =
+                mService.registerDataUsageCallback(callingPackage, inputRequest,
+                        messenger, mockBinder);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
+        assertEquals(minThresholdInBytes, request.thresholdInBytes);
+
+        // Send dummy message to make sure that any previous message has been handled
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+
+        verifyAndReset();
+
+        // Make sure that the caller binder gets connected
+        EasyMock.verify(mockBinder);
+        EasyMock.reset(mockBinder);
+
+        // modify some number on wifi, and trigger poll event
+        // not enough traffic to call data usage callback
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+
+        replay();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        verifyAndReset();
+        assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+
+        // make sure callback has not being called
+        assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+
+        // and bump forward again, with counters going higher. this is
+        // important, since it will trigger the data usage callback
+        incrementCurrentTime(DAY_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+
+        replay();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
+        verifyAndReset();
+
+        // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+        cv.close();
+
+        // Allow binder to disconnect
+        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
+                .andReturn(true);
+        EasyMock.replay(mockBinder);
+
+        // Unregister request
+        mService.unregisterDataUsageRequest(request);
+
+        // Wait for the caller to ack receipt of CALLBACK_RELEASED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+
+        // Make sure that the caller binder gets disconnected
+        EasyMock.verify(mockBinder);
+    }
+
+    public void testRegisterDataUsageCallback_uids() throws Exception {
+        // pretend that network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
+
+        replay();
+        mService.forceUpdateIfaces();
+        verifyAndReset();
+
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        int[] uids = new int[] { UID_RED };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, thresholdInBytes);
+
+        // Create a messenger that waits for callback activity
+        ConditionVariable cv = new ConditionVariable(false);
+        cv.close();
+        LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
+        Messenger messenger = new Messenger(latchedHandler);
+
+        // Allow binder to connect
+        IBinder mockBinder = createMock(IBinder.class);
+        mockBinder.linkToDeath((IBinder.DeathRecipient) anyObject(), anyInt());
+        EasyMock.replay(mockBinder);
+
+        // Force poll
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        replay();
+
+        // Register and verify request and that binder was called
+        DataUsageRequest request =
+                mService.registerDataUsageCallback(callingPackage, inputRequest,
+                        messenger, mockBinder);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(thresholdInBytes, request.thresholdInBytes);
+
+        // Wait for service to handle internal MSG_REGISTER_DATA_USAGE_LISTENER
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+
+        verifyAndReset();
+
+        // Make sure that the caller binder gets connected
+        EasyMock.verify(mockBinder);
+        EasyMock.reset(mockBinder);
+
+        // modify some number on mobile interface, and trigger poll event
+        // not enough traffic to call data usage callback
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT, 128L, 2L,
+                        128L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_DEFAULT, 64L, 1L, 64L,
+                        1L, 0L));
+        expectNetworkStatsPoll();
+
+        replay();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+
+        // verify entire history present
+        NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_ROAMING, 128L, 2L,
+                128L, 2L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_ROAMING, 64L, 1L, 64L,
+                1L, 0);
+
+        verifyAndReset();
+
+        // make sure callback has not being called
+        assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+
+        // and bump forward again, with counters going higher. this is
+        // important, since it will trigger the data usage callback
+        incrementCurrentTime(DAY_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_DEFAULT,
+                        128000000L, 2L, 128000000L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_DEFAULT,
+                        64000000L, 1L, 64000000L, 1L, 0L));
+        expectNetworkStatsPoll();
+
+        replay();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 128000000L, 2L, 128000000L, 2L, 0);
+
+        // verify entire history present
+        stats = mSession.getSummaryForAllUid(
+                sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_ROAMING,
+                128000000L, 2L, 128000000L, 2L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_ROAMING,
+                64000000L, 1L, 64000000L, 1L, 0);
+
+        verifyAndReset();
+
+        // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+        cv.close();
+
+        // Allow binder to disconnect
+        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
+                .andReturn(true);
+        EasyMock.replay(mockBinder);
+
+        // Unregister request
+        mService.unregisterDataUsageRequest(request);
+
+        // Wait for the caller to ack receipt of CALLBACK_RELEASED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+
+        // Make sure that the caller binder gets disconnected
+        EasyMock.verify(mockBinder);
+    }
+
+    public void testUnregisterDataUsageCallback_unknown_noop() throws Exception {
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        DataUsageRequest unknownRequest = new DataUsageRequest(
+                2, templates, null /* uids */, thresholdInBytes);
+
+        mService.unregisterDataUsageRequest(unknownRequest);
+    }
+
+    private static File getBaseDir(File statsDir) {
+        File baseDir = new File(statsDir, "netstats");
+        baseDir.mkdirs();
+        return baseDir;
+    }
+
     private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) throws Exception {
         assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
@@ -894,16 +1207,6 @@
     }
 
     private void expectSystemReady() throws Exception {
-        mAlarmManager.remove(isA(PendingIntent.class), EasyMock.<IAlarmListener>isNull());
-        expectLastCall().anyTimes();
-
-        mAlarmManager.set(eq(getContext().getPackageName()),
-                eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                anyInt(), isA(PendingIntent.class), EasyMock.<IAlarmListener>isNull(),
-                EasyMock.<String>isNull(), EasyMock.<WorkSource>isNull(),
-                EasyMock.<AlarmManager.AlarmClockInfo>isNull());
-        expectLastCall().anyTimes();
-
         mNetManager.setGlobalAlert(anyLong());
         expectLastCall().atLeastOnce();
 
@@ -1093,11 +1396,75 @@
     }
 
     private void replay() {
-        EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+        EasyMock.replay(mNetManager, mTime, mSettings, mConnManager);
     }
 
     private void verifyAndReset() {
-        EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
-        EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+        EasyMock.verify(mNetManager, mTime, mSettings, mConnManager);
+        EasyMock.reset(mNetManager, mTime, mSettings, mConnManager);
     }
+
+    private void forcePollAndWaitForIdle() {
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        // Send dummy message to make sure that any previous message has been handled
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+    }
+
+    static class LatchedHandler extends Handler {
+        private final ConditionVariable mCv;
+        int mLastMessageType = INVALID_TYPE;
+
+        LatchedHandler(Looper looper, ConditionVariable cv) {
+            super(looper);
+            mCv = cv;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mLastMessageType = msg.what;
+            mCv.open();
+            super.handleMessage(msg);
+        }
+    }
+
+    /**
+     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
+     * will return immediately if the handler is already idle.
+     */
+    static class IdleableHandlerThread extends HandlerThread {
+        private IdleHandler mIdleHandler;
+
+        public IdleableHandlerThread(String name) {
+            super(name);
+        }
+
+        public void waitForIdle(long timeoutMs) {
+            final ConditionVariable cv = new ConditionVariable();
+            final MessageQueue queue = getLooper().getQueue();
+
+            synchronized (queue) {
+                if (queue.isIdle()) {
+                    return;
+                }
+
+                assertNull("BUG: only one idle handler allowed", mIdleHandler);
+                mIdleHandler = new IdleHandler() {
+                    public boolean queueIdle() {
+                        cv.open();
+                        mIdleHandler = null;
+                        return false;  // Remove the handler.
+                    }
+                };
+                queue.addIdleHandler(mIdleHandler);
+            }
+
+            if (!cv.block(timeoutMs)) {
+                fail("HandlerThread " + getName() + " did not become idle after " + timeoutMs
+                        + " ms");
+                queue.removeIdleHandler(mIdleHandler);
+            }
+        }
+    }
+
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index acc752a..8e891bf 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -431,17 +431,17 @@
             List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
                     PackageManager.MATCH_DISABLED_COMPONENTS,
                     userId);
-            synchronized (mLock) {
-                final int packageCount = packages.size();
-                for (int p = 0; p < packageCount; p++) {
-                    final PackageInfo pi = packages.get(p);
-                    final String packageName = pi.packageName;
-                    final boolean isIdle = isAppIdleFiltered(packageName,
-                            UserHandle.getAppId(pi.applicationInfo.uid),
-                            userId, elapsedRealtime);
-                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                            userId, isIdle ? 1 : 0, packageName));
-                    if (isIdle) {
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final PackageInfo pi = packages.get(p);
+                final String packageName = pi.packageName;
+                final boolean isIdle = isAppIdleFiltered(packageName,
+                        UserHandle.getAppId(pi.applicationInfo.uid),
+                        userId, elapsedRealtime);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+                if (isIdle) {
+                    synchronized (mLock) {
                         mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
                     }
                 }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bec1e4f..84cffe1 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -191,6 +191,11 @@
     }
 
     @Override
+    public File getDataDir() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getFilesDir() {
         throw new UnsupportedOperationException();
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 4b89217..17ab2ff5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1382,6 +1382,12 @@
     }
 
     @Override
+    public File getDataDir() {
+        // pass
+        return null;
+    }
+
+    @Override
     public File getFilesDir() {
         // pass
         return null;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 189ecdc..9e390f6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -308,6 +308,10 @@
             "java.nio.charset.Charsets",                       "java.nio.charset.StandardCharsets",
             "java.lang.IntegralToString",                      "com.android.tools.layoutlib.java.IntegralToString",
             "java.lang.UnsafeByteSequence",                    "com.android.tools.layoutlib.java.UnsafeByteSequence",
+            // Use android.icu.text versions of DateFormat and SimpleDateFormat since the
+            // original ones do not match the Android implementation
+            "java.text.DateFormat",                            "android.icu.text.DateFormat",
+            "java.text.SimpleDateFormat",                      "android.icu.text.SimpleDateFormat"
         };
 
     private final static String[] EXCLUDED_CLASSES =