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 =