Merge "Extends MtpDatabase so that it can handle child documents."
diff --git a/api/current.txt b/api/current.txt
index 3b4e3a9..62f0113 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34268,6 +34268,18 @@
public deprecated class FloatMath {
}
+ public abstract class FloatProperty extends android.util.Property {
+ ctor public FloatProperty(java.lang.String);
+ method public final void set(T, java.lang.Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public abstract class IntProperty extends android.util.Property {
+ ctor public IntProperty(java.lang.String);
+ method public final void set(T, java.lang.Integer);
+ method public abstract void setValue(T, int);
+ }
+
public final class JsonReader implements java.io.Closeable {
ctor public JsonReader(java.io.Reader);
method public void beginArray() throws java.io.IOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 9db70e3..4118f33 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20523,6 +20523,7 @@
public class X509TrustManagerExtensions {
ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String, java.lang.String) throws java.security.cert.CertificateException;
+ method public boolean isSameTrustConfiguration(java.lang.String, java.lang.String);
method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
}
@@ -36565,6 +36566,18 @@
public deprecated class FloatMath {
}
+ public abstract class FloatProperty extends android.util.Property {
+ ctor public FloatProperty(java.lang.String);
+ method public final void set(T, java.lang.Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public abstract class IntProperty extends android.util.Property {
+ ctor public IntProperty(java.lang.String);
+ method public final void set(T, java.lang.Integer);
+ method public abstract void setValue(T, int);
+ }
+
public final class JsonReader implements java.io.Closeable {
ctor public JsonReader(java.io.Reader);
method public void beginArray() throws java.io.IOException;
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 12780a8..7f33cb5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -18,7 +18,7 @@
package com.android.commands.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.RESIZE_MODE_USER;
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 2960cdc..ab781bb 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -25,7 +25,6 @@
import android.accounts.IAccountManager;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -34,30 +33,25 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
-import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
-import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerificationParams;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IUserManager;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -80,11 +74,6 @@
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.WeakHashMap;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@@ -96,9 +85,6 @@
IUserManager mUm;
IAccountManager mAm;
- private WeakHashMap<String, Resources> mResourceCache
- = new WeakHashMap<String, Resources>();
-
private String[] mArgs;
private int mNextArg;
private String mCurArgData;
@@ -275,10 +261,10 @@
if (args.length == 1) {
if (args[0].equalsIgnoreCase("-l")) {
validCommand = true;
- return runListPackages(false);
- } else if (args[0].equalsIgnoreCase("-lf")){
+ return runShellCommand("package", new String[] { "list", "package" });
+ } else if (args[0].equalsIgnoreCase("-lf")) {
validCommand = true;
- return runListPackages(true);
+ return runShellCommand("package", new String[] { "list", "package", "-f" });
}
} else if (args.length == 2) {
if (args[0].equalsIgnoreCase("-p")) {
@@ -297,6 +283,22 @@
}
}
+ private int runShellCommand(String serviceName, String[] args) {
+ final HandlerThread handlerThread = new HandlerThread("results");
+ handlerThread.start();
+ try {
+ ServiceManager.getService(serviceName).shellCommand(
+ FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ args, new ResultReceiver(new Handler(handlerThread.getLooper())));
+ return 0;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } finally {
+ handlerThread.quitSafely();
+ }
+ return -1;
+ }
+
/**
* Execute the list sub-command.
*
@@ -308,462 +310,11 @@
* pm list instrumentation
*/
private int runList() {
- String type = nextArg();
- if (type == null) {
- System.err.println("Error: didn't specify type of data to list");
- return 1;
+ final String type = nextArg();
+ if ("users".equals(type)) {
+ return runShellCommand("user", new String[] { "list" });
}
- if ("package".equals(type) || "packages".equals(type)) {
- return runListPackages(false);
- } else if ("permission-groups".equals(type)) {
- return runListPermissionGroups();
- } else if ("permissions".equals(type)) {
- return runListPermissions();
- } else if ("features".equals(type)) {
- return runListFeatures();
- } else if ("libraries".equals(type)) {
- return runListLibraries();
- } else if ("instrumentation".equals(type)) {
- return runListInstrumentation();
- } else if ("users".equals(type)) {
- return runListUsers();
- } else {
- System.err.println("Error: unknown list type '" + type + "'");
- return 1;
- }
- }
-
- /**
- * Lists all the installed packages.
- */
- private int runListPackages(boolean showApplicationPackage) {
- int getFlags = 0;
- boolean listDisabled = false, listEnabled = false;
- boolean listSystem = false, listThirdParty = false;
- boolean listInstaller = false;
- int userId = UserHandle.USER_SYSTEM;
- try {
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-l")) {
- // old compat
- } else if (opt.equals("-lf")) {
- showApplicationPackage = true;
- } else if (opt.equals("-f")) {
- showApplicationPackage = true;
- } else if (opt.equals("-d")) {
- listDisabled = true;
- } else if (opt.equals("-e")) {
- listEnabled = true;
- } else if (opt.equals("-s")) {
- listSystem = true;
- } else if (opt.equals("-3")) {
- listThirdParty = true;
- } else if (opt.equals("-i")) {
- listInstaller = true;
- } else if (opt.equals("--user")) {
- userId = Integer.parseInt(nextArg());
- } else if (opt.equals("-u")) {
- getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
- } catch (RuntimeException ex) {
- System.err.println("Error: " + ex.toString());
- return 1;
- }
-
- String filter = nextArg();
-
- try {
- final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags, userId);
-
- int count = packages.size();
- for (int p = 0 ; p < count ; p++) {
- PackageInfo info = packages.get(p);
- if (filter != null && !info.packageName.contains(filter)) {
- continue;
- }
- final boolean isSystem =
- (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
- if ((!listDisabled || !info.applicationInfo.enabled) &&
- (!listEnabled || info.applicationInfo.enabled) &&
- (!listSystem || isSystem) &&
- (!listThirdParty || !isSystem)) {
- System.out.print("package:");
- if (showApplicationPackage) {
- System.out.print(info.applicationInfo.sourceDir);
- System.out.print("=");
- }
- System.out.print(info.packageName);
- if (listInstaller) {
- System.out.print(" installer=");
- System.out.print(mPm.getInstallerPackageName(info.packageName));
- }
- System.out.println();
- }
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- @SuppressWarnings("unchecked")
- private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId)
- throws RemoteException {
- ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId);
- return slice.getList();
- }
-
- /**
- * Lists all of the features supported by the current device.
- *
- * pm list features
- */
- private int runListFeatures() {
- try {
- List<FeatureInfo> list = new ArrayList<FeatureInfo>();
- FeatureInfo[] rawList = mPm.getSystemAvailableFeatures();
- for (int i=0; i<rawList.length; i++) {
- list.add(rawList[i]);
- }
-
-
- // Sort by name
- Collections.sort(list, new Comparator<FeatureInfo>() {
- public int compare(FeatureInfo o1, FeatureInfo o2) {
- if (o1.name == o2.name) return 0;
- if (o1.name == null) return -1;
- if (o2.name == null) return 1;
- return o1.name.compareTo(o2.name);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- FeatureInfo fi = list.get(p);
- System.out.print("feature:");
- if (fi.name != null) System.out.println(fi.name);
- else System.out.println("reqGlEsVersion=0x"
- + Integer.toHexString(fi.reqGlEsVersion));
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all of the libraries supported by the current device.
- *
- * pm list libraries
- */
- private int runListLibraries() {
- try {
- List<String> list = new ArrayList<String>();
- String[] rawList = mPm.getSystemSharedLibraryNames();
- for (int i=0; i<rawList.length; i++) {
- list.add(rawList[i]);
- }
-
-
- // Sort by name
- Collections.sort(list, new Comparator<String>() {
- public int compare(String o1, String o2) {
- if (o1 == o2) return 0;
- if (o1 == null) return -1;
- if (o2 == null) return 1;
- return o1.compareTo(o2);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- String lib = list.get(p);
- System.out.print("library:");
- System.out.println(lib);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all of the installed instrumentation, or all for a given package
- *
- * pm list instrumentation [package] [-f]
- */
- private int runListInstrumentation() {
- int flags = 0; // flags != 0 is only used to request meta-data
- boolean showPackage = false;
- String targetPackage = null;
-
- try {
- String opt;
- while ((opt=nextArg()) != null) {
- if (opt.equals("-f")) {
- showPackage = true;
- } else if (opt.charAt(0) != '-') {
- targetPackage = opt;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
- } catch (RuntimeException ex) {
- System.err.println("Error: " + ex.toString());
- return 1;
- }
-
- try {
- List<InstrumentationInfo> list = mPm.queryInstrumentation(targetPackage, flags);
-
- // Sort by target package
- Collections.sort(list, new Comparator<InstrumentationInfo>() {
- public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
- return o1.targetPackage.compareTo(o2.targetPackage);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- InstrumentationInfo ii = list.get(p);
- System.out.print("instrumentation:");
- if (showPackage) {
- System.out.print(ii.sourceDir);
- System.out.print("=");
- }
- ComponentName cn = new ComponentName(ii.packageName, ii.name);
- System.out.print(cn.flattenToShortString());
- System.out.print(" (target=");
- System.out.print(ii.targetPackage);
- System.out.println(")");
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all the known permission groups.
- */
- private int runListPermissionGroups() {
- try {
- List<PermissionGroupInfo> pgs = mPm.getAllPermissionGroups(0);
-
- int count = pgs.size();
- for (int p = 0 ; p < count ; p++) {
- PermissionGroupInfo pgi = pgs.get(p);
- System.out.print("permission group:");
- System.out.println(pgi.name);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized) {
- if (nonLocalized != null) {
- return nonLocalized.toString();
- }
- if (res != 0) {
- Resources r = getResources(pii);
- if (r != null) {
- try {
- return r.getString(res);
- } catch (Resources.NotFoundException e) {
- }
- }
- }
- return null;
- }
-
- /**
- * Lists all the permissions in a group.
- */
- private int runListPermissions() {
- try {
- boolean labels = false;
- boolean groups = false;
- boolean userOnly = false;
- boolean summary = false;
- boolean dangerousOnly = false;
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-f")) {
- labels = true;
- } else if (opt.equals("-g")) {
- groups = true;
- } else if (opt.equals("-s")) {
- groups = true;
- labels = true;
- summary = true;
- } else if (opt.equals("-u")) {
- userOnly = true;
- } else if (opt.equals("-d")) {
- dangerousOnly = true;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- String grp = nextArg();
- ArrayList<String> groupList = new ArrayList<String>();
- if (groups) {
- List<PermissionGroupInfo> infos =
- mPm.getAllPermissionGroups(0);
- for (int i=0; i<infos.size(); i++) {
- groupList.add(infos.get(i).name);
- }
- groupList.add(null);
- } else {
- groupList.add(grp);
- }
-
- if (dangerousOnly) {
- System.out.println("Dangerous Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_DANGEROUS,
- PermissionInfo.PROTECTION_DANGEROUS);
- if (userOnly) {
- System.out.println("Normal Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_NORMAL,
- PermissionInfo.PROTECTION_NORMAL);
- }
- } else if (userOnly) {
- System.out.println("Dangerous and Normal Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_NORMAL,
- PermissionInfo.PROTECTION_DANGEROUS);
- } else {
- System.out.println("All Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- -10000, 10000);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private void doListPermissions(ArrayList<String> groupList,
- boolean groups, boolean labels, boolean summary,
- int startProtectionLevel, int endProtectionLevel)
- throws RemoteException {
- for (int i=0; i<groupList.size(); i++) {
- String groupName = groupList.get(i);
- String prefix = "";
- if (groups) {
- if (i > 0) System.out.println("");
- if (groupName != null) {
- PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(
- groupName, 0);
- if (summary) {
- Resources res = getResources(pgi);
- if (res != null) {
- System.out.print(loadText(pgi, pgi.labelRes,
- pgi.nonLocalizedLabel) + ": ");
- } else {
- System.out.print(pgi.name + ": ");
-
- }
- } else {
- System.out.println((labels ? "+ " : "")
- + "group:" + pgi.name);
- if (labels) {
- System.out.println(" package:" + pgi.packageName);
- Resources res = getResources(pgi);
- if (res != null) {
- System.out.println(" label:"
- + loadText(pgi, pgi.labelRes,
- pgi.nonLocalizedLabel));
- System.out.println(" description:"
- + loadText(pgi, pgi.descriptionRes,
- pgi.nonLocalizedDescription));
- }
- }
- }
- } else {
- System.out.println(((labels && !summary)
- ? "+ " : "") + "ungrouped:");
- }
- prefix = " ";
- }
- List<PermissionInfo> ps = mPm.queryPermissionsByGroup(
- groupList.get(i), 0);
- int count = ps.size();
- boolean first = true;
- for (int p = 0 ; p < count ; p++) {
- PermissionInfo pi = ps.get(p);
- if (groups && groupName == null && pi.group != null) {
- continue;
- }
- final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- if (base < startProtectionLevel
- || base > endProtectionLevel) {
- continue;
- }
- if (summary) {
- if (first) {
- first = false;
- } else {
- System.out.print(", ");
- }
- Resources res = getResources(pi);
- if (res != null) {
- System.out.print(loadText(pi, pi.labelRes,
- pi.nonLocalizedLabel));
- } else {
- System.out.print(pi.name);
- }
- } else {
- System.out.println(prefix + (labels ? "+ " : "")
- + "permission:" + pi.name);
- if (labels) {
- System.out.println(prefix + " package:" + pi.packageName);
- Resources res = getResources(pi);
- if (res != null) {
- System.out.println(prefix + " label:"
- + loadText(pi, pi.labelRes,
- pi.nonLocalizedLabel));
- System.out.println(prefix + " description:"
- + loadText(pi, pi.descriptionRes,
- pi.nonLocalizedDescription));
- }
- System.out.println(prefix + " protectionLevel:"
- + PermissionInfo.protectionToString(pi.protectionLevel));
- }
- }
- }
-
- if (summary) {
- System.out.println("");
- }
- }
+ return runShellCommand("package", mArgs);
}
private int runPath() {
@@ -1467,29 +1018,6 @@
}
}
- public int runListUsers() {
- try {
- IActivityManager am = ActivityManagerNative.getDefault();
-
- List<UserInfo> users = mUm.getUsers(false);
- if (users == null) {
- System.err.println("Error: couldn't get users");
- return 1;
- } else {
- System.out.println("Users:");
- for (int i = 0; i < users.size(); i++) {
- String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
- System.out.println("\t" + users.get(i).toString() + running);
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
public int runGetMaxUsers() {
System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
return 0;
@@ -1997,24 +1525,6 @@
return 1;
}
- private Resources getResources(PackageItemInfo pii) {
- Resources res = mResourceCache.get(pii.packageName);
- if (res != null) return res;
-
- try {
- ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0, 0);
- AssetManager am = new AssetManager();
- am.addAssetPath(ai.publicSourceDir);
- res = new Resources(am, null, null);
- mResourceCache.put(pii.packageName, res);
- return res;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return null;
- }
- }
-
private static String checkAbiArgument(String abi) {
if (TextUtils.isEmpty(abi)) {
throw new IllegalArgumentException("Missing ABI argument");
@@ -2110,14 +1620,7 @@
}
private static int showUsage() {
- System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
- System.err.println(" pm list permission-groups");
- System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
- System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]");
- System.err.println(" pm list features");
- System.err.println(" pm list libraries");
- System.err.println(" pm list users");
- System.err.println(" pm path [--user USER_ID] PACKAGE");
+ System.err.println("usage: pm path [--user USER_ID] PACKAGE");
System.err.println(" pm dump PACKAGE");
System.err.println(" pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
@@ -2151,34 +1654,8 @@
System.err.println(" pm remove-user USER_ID");
System.err.println(" pm get-max-users");
System.err.println("");
- System.err.println("pm list packages: prints all packages, optionally only");
- System.err.println(" those whose package name contains the text in FILTER. Options:");
- System.err.println(" -f: see their associated file.");
- System.err.println(" -d: filter to only show disbled packages.");
- System.err.println(" -e: filter to only show enabled packages.");
- System.err.println(" -s: filter to only show system packages.");
- System.err.println(" -3: filter to only show third party packages.");
- System.err.println(" -i: see the installer for the packages.");
- System.err.println(" -u: also include uninstalled packages.");
- System.err.println("");
- System.err.println("pm list permission-groups: prints all known permission groups.");
- System.err.println("");
- System.err.println("pm list permissions: prints all known permissions, optionally only");
- System.err.println(" those in GROUP. Options:");
- System.err.println(" -g: organize by group.");
- System.err.println(" -f: print all information.");
- System.err.println(" -s: short summary.");
- System.err.println(" -d: only list dangerous permissions.");
- System.err.println(" -u: list only the permissions users will see.");
- System.err.println("");
- System.err.println("pm list instrumentation: use to list all test packages; optionally");
- System.err.println(" supply <TARGET-PACKAGE> to list the test packages for a particular");
- System.err.println(" application. Options:");
- System.err.println(" -f: list the .apk file for the test package.");
- System.err.println("");
- System.err.println("pm list features: prints all features of the system.");
- System.err.println("");
- System.err.println("pm list users: prints all users on the system.");
+ System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
+ System.err.println(" to display the new commands.");
System.err.println("");
System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
System.err.println("");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2f0849f..472d97f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2728,8 +2728,8 @@
/**
* Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
- * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
- * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
*
* @param stackId stack Id to change to.
* @hide
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fce1b2e..b809baa 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,7 +73,6 @@
*/
public class ActivityManager {
private static String TAG = "ActivityManager";
- private static boolean localLOGV = false;
private static int gMaxRecentTasks = -1;
@@ -397,60 +396,112 @@
*/
public static final int COMPAT_MODE_TOGGLE = 2;
- /**
- * Invalid stack ID.
- * @hide
- */
- public static final int INVALID_STACK_ID = -1;
+ /** @hide */
+ public static class StackId {
+ /** Invalid stack ID. */
+ public static final int INVALID_STACK_ID = -1;
- /**
- * First static stack ID.
- * @hide
- */
- public static final int FIRST_STATIC_STACK_ID = 0;
+ /** First static stack ID. */
+ public static final int FIRST_STATIC_STACK_ID = 0;
- /**
- * Home activity stack ID.
- * @hide
- */
- public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
+ /** Home activity stack ID. */
+ public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
- /**
- * ID of stack where fullscreen activities are normally launched into.
- * @hide
- */
- public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+ /** ID of stack where fullscreen activities are normally launched into. */
+ public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
- /**
- * ID of stack where freeform/resized activities are normally launched into.
- * @hide
- */
- public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
+ /** ID of stack where freeform/resized activities are normally launched into. */
+ public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
- /**
- * ID of stack that occupies a dedicated region of the screen.
- * @hide
- */
- public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
+ /** ID of stack that occupies a dedicated region of the screen. */
+ public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
- /**
- * ID of stack that always on top (always visible) when it exist.
- * Mainly used for this in Picture-in-Picture mode.
- * @hide
- */
- public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
+ /** ID of stack that always on top (always visible) when it exist. */
+ public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
- /**
- * Last static stack stack ID.
- * @hide
- */
- public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
+ /** Last static stack stack ID. */
+ public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
- /**
- * Start of ID range used by stacks that are created dynamically.
- * @hide
- */
- public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+ /** Start of ID range used by stacks that are created dynamically. */
+ public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+
+ public static boolean isStaticStack(int stackId) {
+ return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
+ }
+
+ /**
+ * Returns true if the activities contained in the input stack display a shadow around
+ * their border.
+ */
+ public static boolean hasWindowShadow(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the activities contained in the input stack display a decor view.
+ */
+ public static boolean hasWindowDecor(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ }
+
+ /**
+ * Returns true if the tasks contained in the stack can be resized independently of the
+ * stack.
+ */
+ public static boolean isTaskResizeAllowed(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ }
+
+ /**
+ * Returns true if the task bounds should persist across power cycles.
+ */
+ public static boolean persistTaskBounds(int stackId) {
+ return isStaticStack(stackId) &&
+ stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if dynamic stacks are allowed to be visible behind the input stack.
+ */
+ public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
+ return stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if we try to maintain focus in the current stack when the top activity
+ * finishes.
+ */
+ public static boolean keepFocusInStackIfPossible(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID
+ || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if Stack size is affected by the docked stack changing size.
+ */
+ public static boolean isResizeableByDockedStack(int stackId) {
+ return isStaticStack(stackId) &&
+ stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the size of tasks in the input stack are affected by the docked stack
+ * changing size.
+ */
+ public static boolean isTaskResizeableByDockedStack(int stackId) {
+ return isStaticStack(stackId) && stackId != FREEFORM_WORKSPACE_STACK_ID
+ && stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the windows of tasks being moved to this stack should be preserved so
+ * there isn't a display gap.
+ */
+ public static boolean preserveWindowOnTaskMove(int stackId) {
+ return stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+ }
/**
* Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
@@ -1016,6 +1067,12 @@
*/
public int numActivities;
+ /**
+ * The bounds of the task.
+ * @hide
+ */
+ public Rect bounds;
+
public RecentTaskInfo() {
}
@@ -1053,6 +1110,12 @@
ComponentName.writeToParcel(baseActivity, dest);
ComponentName.writeToParcel(topActivity, dest);
dest.writeInt(numActivities);
+ if (bounds != null) {
+ dest.writeInt(1);
+ bounds.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
}
public void readFromParcel(Parcel source) {
@@ -1073,6 +1136,8 @@
baseActivity = ComponentName.readFromParcel(source);
topActivity = ComponentName.readFromParcel(source);
numActivities = source.readInt();
+ bounds = source.readInt() > 0 ?
+ Rect.CREATOR.createFromParcel(source) : null;
}
public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09c0a6e..77a9795 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -653,7 +653,7 @@
null, //WRITE_SETTINGS
UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
null, //ACCESS_NOTIFICATIONS
- null, //CAMERA
+ UserManager.DISALLOW_CAMERA, //CAMERA
UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
null, //PLAY_AUDIO
null, //READ_CLIPBOARD
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49edff4..ffeb6ed 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3829,7 +3829,9 @@
@Override
public void purgeResources() {
super.purgeResources();
- if (mPicture != null && mPicture.isMutable()) {
+ if (mPicture != null &&
+ mPicture.isMutable() &&
+ mPicture.getAllocationByteCount() >= (128 * (1 << 10))) {
mPicture = mPicture.createAshmemBitmap();
}
if (mBigLargeIcon != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9e9d949..0fdf3d3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2271,6 +2271,8 @@
* on the device, for this user. After setting this, no applications running as this user
* will be able to access any cameras on the device.
*
+ * <p>If the caller is device owner, then the restriction will be applied to all users.
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call
* this method; if it has not, a security exception will be thrown.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 948ea1e..498ff81 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -71,10 +71,11 @@
* Could be hours, could be days, who knows?
*
* @param packageName
+ * @param uidForAppId The uid of the app, which will be used for its app id
* @param userId
* @return
*/
- public abstract boolean isAppIdle(String packageName, int userId);
+ public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
/**
* Returns all of the uids for a given user where all packages associating with that uid
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 27ecf9f..c57fc89 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1125,7 +1125,7 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
- * The device has professional audio level of functionality, performance, and acoustics.
+ * The device has professional audio level of functionality and performance.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index eb4ceda..25ef8b5 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,6 +16,8 @@
package android.net.http;
+import android.annotation.SystemApi;
+
import com.android.org.conscrypt.TrustManagerImpl;
import java.security.cert.CertificateException;
@@ -80,4 +82,15 @@
public boolean isUserAddedCertificate(X509Certificate cert) {
return mDelegate.isUserAddedCertificate(cert);
}
+
+ /**
+ * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
+ * hostnames.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
+ return true;
+ }
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 1f3e9a7..56cb250 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -108,6 +108,13 @@
*/
public static final String EXTRA_MAX_CHARGING_CURRENT = "max_charging_current";
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value set to the maximum charging voltage supported by the charger in micro volts.
+ * {@hide}
+ */
+ public static final String EXTRA_MAX_CHARGING_VOLTAGE = "max_charging_voltage";
+
// values for "status" field in the ACTION_BATTERY_CHANGED Intent
public static final int BATTERY_STATUS_UNKNOWN = 1;
public static final int BATTERY_STATUS_CHARGING = 2;
diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java
index 29e868c..c3e0f24 100644
--- a/core/java/android/os/BatteryProperties.java
+++ b/core/java/android/os/BatteryProperties.java
@@ -23,6 +23,7 @@
public boolean chargerUsbOnline;
public boolean chargerWirelessOnline;
public int maxChargingCurrent;
+ public int maxChargingVoltage;
public int batteryStatus;
public int batteryHealth;
public boolean batteryPresent;
@@ -39,6 +40,7 @@
chargerUsbOnline = other.chargerUsbOnline;
chargerWirelessOnline = other.chargerWirelessOnline;
maxChargingCurrent = other.maxChargingCurrent;
+ maxChargingVoltage = other.maxChargingVoltage;
batteryStatus = other.batteryStatus;
batteryHealth = other.batteryHealth;
batteryPresent = other.batteryPresent;
@@ -58,6 +60,7 @@
chargerUsbOnline = p.readInt() == 1 ? true : false;
chargerWirelessOnline = p.readInt() == 1 ? true : false;
maxChargingCurrent = p.readInt();
+ maxChargingVoltage = p.readInt();
batteryStatus = p.readInt();
batteryHealth = p.readInt();
batteryPresent = p.readInt() == 1 ? true : false;
@@ -72,6 +75,7 @@
p.writeInt(chargerUsbOnline ? 1 : 0);
p.writeInt(chargerWirelessOnline ? 1 : 0);
p.writeInt(maxChargingCurrent);
+ p.writeInt(maxChargingVoltage);
p.writeInt(batteryStatus);
p.writeInt(batteryHealth);
p.writeInt(batteryPresent ? 1 : 0);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2e31ab6..e892349 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -487,6 +487,16 @@
public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
/**
+ * Specifies if a user is not allowed to use the camera.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ * @hide
+ */
+ public static final String DISALLOW_CAMERA = "no_camera";
+
+ /**
* Allows apps in the parent profile to handle web links from the managed profile.
*
* This user restriction has an effect only in a managed profile.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index d7be6d8..b50cc79 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -36,7 +36,7 @@
*
* Must be called while taking the {@link #getUserRestrictionsLock()} lock.
*/
- public abstract void updateEffectiveUserRestrictionsRL(int userId);
+ public abstract void updateEffectiveUserRestrictionsLR(int userId);
/**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
@@ -44,7 +44,7 @@
*
* Must be called while taking the {@link #getUserRestrictionsLock()} lock.
*/
- public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+ public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
/**
* Returns the "base" user restrictions.
diff --git a/core/java/android/util/FloatProperty.java b/core/java/android/util/FloatProperty.java
index a67b3cb..4aac196 100644
--- a/core/java/android/util/FloatProperty.java
+++ b/core/java/android/util/FloatProperty.java
@@ -15,18 +15,14 @@
*/
package android.util;
-import android.util.Property;
-
/**
* An implementation of {@link android.util.Property} to be used specifically with fields of type
* <code>float</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Float) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, float) setValue()} function that takes the primitive
* <code>float</code> type and avoids autoboxing and other overhead associated with the
* <code>Float</code> class.
*
* @param <T> The class on which the Property is declared.
- *
- * @hide
*/
public abstract class FloatProperty<T> extends Property<T, Float> {
@@ -35,7 +31,7 @@
}
/**
- * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+ * A type-specific variant of {@link #set(Object, Float)} that is faster when dealing
* with fields of type <code>float</code>.
*/
public abstract void setValue(T object, float value);
diff --git a/core/java/android/util/IntProperty.java b/core/java/android/util/IntProperty.java
index 17977ca..9e21ced 100644
--- a/core/java/android/util/IntProperty.java
+++ b/core/java/android/util/IntProperty.java
@@ -15,18 +15,14 @@
*/
package android.util;
-import android.util.Property;
-
/**
* An implementation of {@link android.util.Property} to be used specifically with fields of type
* <code>int</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, int) setValue()} function that takes the primitive
* <code>int</code> type and avoids autoboxing and other overhead associated with the
* <code>Integer</code> class.
*
* @param <T> The class on which the Property is declared.
- *
- * @hide
*/
public abstract class IntProperty<T> extends Property<T, Integer> {
@@ -35,7 +31,7 @@
}
/**
- * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
+ * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing
* with fields of type <code>int</code>.
*/
public abstract void setValue(T object, int value);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f18b35d..c01c6df 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -201,6 +201,11 @@
*/
void setScreenCaptureDisabled(int userId, boolean disabled);
+ /**
+ * Cancels the window transitions for the given task.
+ */
+ void cancelTaskWindowTransition(int taskId);
+
// These can only be called with the SET_ORIENTATION permission.
/**
* Update the current screen rotation based on the current state of
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 017364a..1be2f95 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -96,6 +96,23 @@
out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
/**
+ * Position a window relative to it's parent (attached) window without triggering
+ * a full relayout. This action may be deferred until a given frame number
+ * for the parent window appears. This allows for synchronizing movement of a child
+ * to repainting the contents of the parent.
+ *
+ * @param window The window being modified. Must be attached to a parent window
+ * or this call will fail.
+ * @param x The new x position
+ * @param y The new y position
+ * @param deferTransactionUntilFrame Frame number from our parent (attached) to
+ * defer this action until.
+ * @param outFrame Rect in which is placed the new position/size on screen.
+ */
+ void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+ out Rect outFrame);
+
+ /**
* If a call to relayout() asked to have the surface destroy deferred,
* it must call this once it is okay to destroy that surface.
*/
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6de4d3e..394660f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -56,6 +56,8 @@
private static native int nativeGetWidth(long nativeObject);
private static native int nativeGetHeight(long nativeObject);
+ private static native long nativeGetNextFrameNumber(long nativeObject);
+
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -220,6 +222,18 @@
}
/**
+ * Returns the next frame number which will be dequeued for rendering.
+ * Intended for use with SurfaceFlinger's deferred transactions API.
+ *
+ * @hide
+ */
+ public long getNextFrameNumber() {
+ synchronized (mLock) {
+ return nativeGetNextFrameNumber(mNativeObject);
+ }
+ }
+
+ /**
* Returns true if the consumer of this Surface is running behind the producer.
*
* @return True if the consumer is more than one buffer ahead of the producer.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bcf9b2c..b58c68f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -81,6 +81,9 @@
private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
private static native void nativeSetDisplayPowerMode(
IBinder displayToken, int mode);
+ private static native void nativeDeferTransactionUntil(long nativeObject,
+ IBinder handle, long frame);
+ private static native IBinder nativeGetHandle(long nativeObject);
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -358,6 +361,14 @@
nativeCloseTransaction();
}
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ }
+
+ public IBinder getHandle() {
+ return nativeGetHandle(mNativeObject);
+ }
+
/** flag the transaction as an animation */
public static void setAnimationTransaction() {
nativeSetAnimationTransaction();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db68c29..dddea21 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -448,11 +448,10 @@
final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
+ final boolean positionChanged = mLeft != mLocation[0] || mTop != mLocation[1];
if (force || creating || formatChanged || sizeChanged || visibleChanged
- || mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
-
if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged
@@ -616,11 +615,22 @@
mSession.performDeferredDestroy(mWindow);
}
} catch (RemoteException ex) {
+ Log.e(TAG, "Exception from relayout", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
" w=" + mLayout.width + " h=" + mLayout.height +
", frame=" + mSurfaceFrame);
+ } else if (positionChanged) { // Only the position has changed
+ mLeft = mLocation[0];
+ mTop = mLocation[1];
+ try {
+ mSession.repositionChild(mWindow, mLeft, mTop,
+ viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
+ mWinFrame);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception from relayout", ex);
+ }
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8b804e8..cd8c084 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5455,7 +5455,7 @@
* @hide
*/
protected boolean performButtonActionOnTouchDown(MotionEvent event) {
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
(event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
showContextMenu(event.getX(), event.getY());
mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e17bdd7..faeb353 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6607,6 +6607,20 @@
}
}
+ long getNextFrameNumber() {
+ long frameNumber = -1;
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.lock();
+ }
+ if (mSurface.isValid()) {
+ frameNumber = mSurface.getNextFrameNumber();
+ }
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.unlock();
+ }
+ return frameNumber;
+ }
+
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5d11c8b..8259372 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -552,8 +552,8 @@
/**
* Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
- * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
- * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
*
* @param stackId stack Id to change to.
*/
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 23e9a0d..ab93505 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -40,9 +40,9 @@
boolean isForMainFrame();
/**
- * Gets whether the request was a result of a redirect.
+ * Gets whether the request was a result of a server-side redirect.
*
- * @return whether the request was a result of a redirect.
+ * @return whether the request was a result of a server-side redirect.
*/
boolean isRedirect();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 584deff..cb18b49 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -96,27 +96,49 @@
public MissingWebViewPackageException(Exception e) { super(e); }
}
- public static String getWebViewPackageName() {
- return AppGlobals.getInitialApplication().getString(
- com.android.internal.R.string.config_webViewPackageName);
+ /** @hide */
+ public static String[] getWebViewPackageNames() {
+ return AppGlobals.getInitialApplication().getResources().getStringArray(
+ com.android.internal.R.array.config_webViewPackageNames);
}
- private static PackageInfo fetchPackageInfo() {
+ // TODO (gsennton) remove when committing webview xts test change
+ public static String getWebViewPackageName() {
+ String[] webViewPackageNames = getWebViewPackageNames();
+ return webViewPackageNames[webViewPackageNames.length-1];
+ }
+
+ /**
+ * Return the package info of the first package in the webview priority list that contains
+ * webview.
+ *
+ * @hide
+ */
+ public static PackageInfo findPreferredWebViewPackage() {
PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
- try {
- return pm.getPackageInfo(getWebViewPackageName(), PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- throw new MissingWebViewPackageException(e);
+
+ for (String packageName : getWebViewPackageNames()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_META_DATA);
+ ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+
+ // If the correct flag is set the package contains webview.
+ if (getWebViewLibrary(applicationInfo) != null) {
+ return packageInfo;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
}
+ throw new MissingWebViewPackageException("Could not find a loadable WebView package");
}
// throws MissingWebViewPackageException
private static ApplicationInfo getWebViewApplicationInfo() {
- if (sPackageInfo == null) {
- return fetchPackageInfo().applicationInfo;
- } else {
+ if (sPackageInfo == null)
+ return findPreferredWebViewPackage().applicationInfo;
+ else
return sPackageInfo.applicationInfo;
- }
}
private static String getWebViewLibrary(ApplicationInfo ai) {
@@ -134,7 +156,12 @@
* name is the same as the one providing the webview.
*/
public static int loadWebViewNativeLibraryFromPackage(String packageName) {
- sPackageInfo = fetchPackageInfo();
+ try {
+ sPackageInfo = findPreferredWebViewPackage();
+ } catch (MissingWebViewPackageException e) {
+ return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ }
+
if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
return loadNativeLibrary();
}
@@ -180,7 +207,7 @@
private static Class<WebViewFactoryProvider> getProviderClass() {
try {
// First fetch the package info so we can log the webview package version.
- sPackageInfo = fetchPackageInfo();
+ sPackageInfo = findPreferredWebViewPackage();
Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 13d046e..830da79 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -21,6 +21,8 @@
import android.os.SystemClock;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
+
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -42,11 +44,22 @@
private OutputStream mOut;
private LocalSocket mSocket;
+ private volatile Object mWarnIfHeld;
+
private final byte buf[] = new byte[1024];
public InstallerConnection() {
}
+ /**
+ * Yell loudly if someone tries making future calls while holding a lock on
+ * the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ Preconditions.checkState(mWarnIfHeld == null);
+ mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+ }
+
public synchronized String transact(String cmd) {
if (!connect()) {
Slog.e(TAG, "connection failed");
@@ -84,6 +97,11 @@
}
public int execute(String cmd) {
+ if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+ }
+
String res = transact(cmd);
try {
return Integer.parseInt(res);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b3bd46d..1bce585 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,11 +16,8 @@
package com.android.internal.policy;
-import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getMode;
@@ -30,6 +27,7 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
import android.app.SearchManager;
import android.os.Build;
@@ -737,9 +735,9 @@
if (mWorkspaceId != workspaceId) {
mWorkspaceId = workspaceId;
// We might have to change the kind of surface before we do anything else.
- mNonClientDecorView.phoneWindowUpdated(hasNonClientDecor(mWorkspaceId),
- nonClientDecorHasShadow(mWorkspaceId));
- mDecor.enableNonClientDecor(hasNonClientDecor(workspaceId));
+ mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
+ StackId.hasWindowShadow(mWorkspaceId));
+ mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
}
}
}
@@ -3735,7 +3733,7 @@
* @return Returns true when the window has a shadow created by the non client decor.
**/
private boolean windowHasShadow() {
- return windowHasNonClientDecor() && nonClientDecorHasShadow(mWindow.mWorkspaceId);
+ return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
}
void setWindow(PhoneWindow phoneWindow) {
@@ -4234,7 +4232,7 @@
mWorkspaceId = getWorkspaceId();
// Only a non floating application window on one of the allowed workspaces can get a non
// client decor.
- if (!isFloating() && isApplication && mWorkspaceId < FIRST_DYNAMIC_STACK_ID) {
+ if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
// Dependent on the brightness of the used title we either use the
// dark or the light button frame.
if (nonClientDecorView == null) {
@@ -4250,12 +4248,13 @@
R.layout.non_client_decor_light, null);
}
}
- nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId),
- nonClientDecorHasShadow(mWorkspaceId), getResizingBackgroundDrawable(),
+ nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
+ StackId.hasWindowShadow(mWorkspaceId), getResizingBackgroundDrawable(),
mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
}
// Tell the decor if it has a visible non client decor.
- mDecor.enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor(mWorkspaceId));
+ mDecor.enableNonClientDecor(
+ nonClientDecorView != null&& StackId.hasWindowDecor(mWorkspaceId));
return nonClientDecorView;
}
@@ -5428,24 +5427,6 @@
return workspaceId;
}
- /**
- * Determines if the window should show a non client decor for the workspace it is in.
- * @param workspaceId The Id of the workspace which contains this window.
- * @Return Returns true if the window should show a non client decor.
- **/
- private static boolean hasNonClientDecor(int workspaceId) {
- return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
- }
-
- /**
- * Determines if the window should show a shadow or not, dependent on the workspace.
- * @param workspaceId The Id of the workspace which contains this window.
- * @Return Returns true if the window should show a shadow.
- **/
- private static boolean nonClientDecorHasShadow(int workspaceId) {
- return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
- }
-
@Override
public void setTheme(int resid) {
mTheme = resid;
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index be9be11..de542b1 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -16,8 +16,9 @@
package com.android.internal.widget;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
@@ -332,8 +333,7 @@
Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
if (callback != null) {
try {
- callback.changeWindowStack(
- android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
+ callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
} catch (RemoteException ex) {
Log.e(TAG, "Cannot change task workspace.");
}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index e6c7c2b..703a9bd 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -28,6 +28,7 @@
#include <cutils/ashmem.h>
#define DEBUG_PARCEL 0
+#define ASHMEM_BITMAP_MIN_SIZE (128 * (1 << 10))
namespace android {
@@ -993,7 +994,7 @@
// Map the bitmap in place from the ashmem region if possible otherwise copy.
Bitmap* nativeBitmap;
- if (blob.fd() >= 0 && (blob.isMutable() || !isMutable)) {
+ if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
#if DEBUG_PARCEL
ALOGD("Bitmap.createFromParcel: mapped contents of %s bitmap from %s blob "
"(fds %s)",
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 41aa9ca..0a8ae2b 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -722,33 +722,33 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gParcelMethods[] = {
- {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
- {"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
- {"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
- {"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
- {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
- {"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
- {"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
+ {"nativeDataSize", "!(J)I", (void*)android_os_Parcel_dataSize},
+ {"nativeDataAvail", "!(J)I", (void*)android_os_Parcel_dataAvail},
+ {"nativeDataPosition", "!(J)I", (void*)android_os_Parcel_dataPosition},
+ {"nativeDataCapacity", "!(J)I", (void*)android_os_Parcel_dataCapacity},
+ {"nativeSetDataSize", "!(JI)J", (void*)android_os_Parcel_setDataSize},
+ {"nativeSetDataPosition", "!(JI)V", (void*)android_os_Parcel_setDataPosition},
+ {"nativeSetDataCapacity", "!(JI)V", (void*)android_os_Parcel_setDataCapacity},
- {"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
- {"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
+ {"nativePushAllowFds", "!(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
+ {"nativeRestoreAllowFds", "!(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
{"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeNative},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
- {"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},
- {"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong},
- {"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat},
- {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
+ {"nativeWriteInt", "!(JI)V", (void*)android_os_Parcel_writeInt},
+ {"nativeWriteLong", "!(JJ)V", (void*)android_os_Parcel_writeLong},
+ {"nativeWriteFloat", "!(JF)V", (void*)android_os_Parcel_writeFloat},
+ {"nativeWriteDouble", "!(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
{"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
- {"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
- {"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong},
- {"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat},
- {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
+ {"nativeReadInt", "!(J)I", (void*)android_os_Parcel_readInt},
+ {"nativeReadLong", "!(J)J", (void*)android_os_Parcel_readLong},
+ {"nativeReadFloat", "!(J)F", (void*)android_os_Parcel_readFloat},
+ {"nativeReadDouble", "!(J)D", (void*)android_os_Parcel_readDouble},
{"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
{"nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
@@ -765,7 +765,7 @@
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
{"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
{"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
- {"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+ {"nativeHasFileDescriptors", "!(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c94bc64..55b7e7e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -2156,9 +2156,9 @@
(void*) android_content_AssetManager_readAsset },
{ "seekAsset", "(JJI)J",
(void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "(J)J",
+ { "getAssetLength", "!(J)J",
(void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "(J)J",
+ { "getAssetRemainingLength", "!(J)J",
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath },
@@ -2174,25 +2174,25 @@
(void*) android_content_AssetManager_getLocales },
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
+ { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","(I)Ljava/lang/String;",
+ { "getResourceName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","(I)Ljava/lang/String;",
+ { "getResourcePackageName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","(I)Ljava/lang/String;",
+ { "getResourceTypeName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","(I)Ljava/lang/String;",
+ { "getResourceEntryName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
+ { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
+ { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","()I",
+ { "getStringBlockCount","!()I",
(void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","(I)J",
+ { "getNativeStringBlock","!(I)J",
(void*) android_content_AssetManager_getNativeStringBlock },
{ "getCookieName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getCookieName },
@@ -2210,21 +2210,21 @@
(void*) android_content_AssetManager_copyTheme },
{ "clearTheme", "(J)V",
(void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
+ { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "(J)I",
+ { "getThemeChangingConfigurations", "!(J)I",
(void*) android_content_AssetManager_getThemeChangingConfigurations },
{ "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
(void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","(JIIJ[I[I[I)Z",
+ { "applyStyle","!(JIIJ[I[I[I)Z",
(void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","(JII[I[I[I[I)Z",
+ { "resolveAttrs","!(JII[I[I[I[I)Z",
(void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","(J[I[I[I)Z",
+ { "retrieveAttributes","!(J[I[I[I)Z",
(void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","(I)I",
+ { "getArraySize","!(I)I",
(void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","(I[I)I",
+ { "retrieveArray","!(I[I)I",
(void*) android_content_AssetManager_retrieveArray },
// XML files.
@@ -2234,11 +2234,11 @@
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","(I)[I",
+ { "getArrayStringInfo","!(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","(I)[I",
+ { "getArrayIntResource","!(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","(I)[I",
+ { "getStyleAttributes","!(I)[I",
(void*) android_content_AssetManager_getStyleAttributes },
// Bookkeeping.
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 7ae51c8..a15c23c 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -372,37 +372,37 @@
(void*) android_content_XmlBlock_nativeGetStringBlock },
{ "nativeCreateParseState", "(J)J",
(void*) android_content_XmlBlock_nativeCreateParseState },
- { "nativeNext", "(J)I",
+ { "nativeNext", "!(J)I",
(void*) android_content_XmlBlock_nativeNext },
- { "nativeGetNamespace", "(J)I",
+ { "nativeGetNamespace", "!(J)I",
(void*) android_content_XmlBlock_nativeGetNamespace },
- { "nativeGetName", "(J)I",
+ { "nativeGetName", "!(J)I",
(void*) android_content_XmlBlock_nativeGetName },
- { "nativeGetText", "(J)I",
+ { "nativeGetText", "!(J)I",
(void*) android_content_XmlBlock_nativeGetText },
- { "nativeGetLineNumber", "(J)I",
+ { "nativeGetLineNumber", "!(J)I",
(void*) android_content_XmlBlock_nativeGetLineNumber },
- { "nativeGetAttributeCount", "(J)I",
+ { "nativeGetAttributeCount", "!(J)I",
(void*) android_content_XmlBlock_nativeGetAttributeCount },
- { "nativeGetAttributeNamespace","(JI)I",
+ { "nativeGetAttributeNamespace","!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeNamespace },
- { "nativeGetAttributeName", "(JI)I",
+ { "nativeGetAttributeName", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeName },
- { "nativeGetAttributeResource", "(JI)I",
+ { "nativeGetAttributeResource", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeResource },
- { "nativeGetAttributeDataType", "(JI)I",
+ { "nativeGetAttributeDataType", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeDataType },
- { "nativeGetAttributeData", "(JI)I",
+ { "nativeGetAttributeData", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeData },
- { "nativeGetAttributeStringValue", "(JI)I",
+ { "nativeGetAttributeStringValue", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeStringValue },
- { "nativeGetAttributeIndex", "(JLjava/lang/String;Ljava/lang/String;)I",
+ { "nativeGetAttributeIndex", "!(JLjava/lang/String;Ljava/lang/String;)I",
(void*) android_content_XmlBlock_nativeGetAttributeIndex },
- { "nativeGetIdAttribute", "(J)I",
+ { "nativeGetIdAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetIdAttribute },
- { "nativeGetClassAttribute", "(J)I",
+ { "nativeGetClassAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetClassAttribute },
- { "nativeGetStyleAttribute", "(J)I",
+ { "nativeGetStyleAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetStyleAttribute },
{ "nativeDestroyParseState", "(J)V",
(void*) android_content_XmlBlock_nativeDestroyParseState },
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index b64acc3..9b41eb3 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -151,10 +151,10 @@
// not in the emulator
return JNI_TRUE;
}
- // In the emulator this property will be set to 1 when hardware GLES is
+ // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
// enabled, 0 otherwise. On old emulator versions it will be undefined.
property_get("ro.kernel.qemu.gles", prop, "0");
- return atoi(prop) == 1 ? JNI_TRUE : JNI_FALSE;
+ return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
}
// ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index da96b93..ff51e4e 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -460,6 +460,10 @@
anw->query(anw, NATIVE_WINDOW_HEIGHT, &value);
return value;
}
+static jlong nativeGetNextFrameNumber(JNIEnv *env, jclass clazz, jlong nativeObject) {
+ Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+ return surface->getNextFrameNumber();
+}
namespace uirenderer {
@@ -536,6 +540,7 @@
(void*)nativeWriteToParcel },
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
+ {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
// HWUI context
{"nHwuiCreate", "(JJ)J", (void*) hwui::create },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 931ad54..1dfe40a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -571,6 +571,21 @@
return JNI_TRUE;
}
+
+static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject handleObject, jlong frameNumber) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ sp<IBinder> handle = ibinderForJavaObject(env, handleObject);
+
+ ctrl->deferTransactionUntil(handle, frameNumber);
+}
+
+static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+ return javaObjectForIBinder(env, ctrl->getHandle());
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -638,6 +653,10 @@
(void*)nativeGetAnimationFrameStats },
{"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
(void*)nativeSetDisplayPowerMode },
+ {"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
+ (void*)nativeDeferTransactionUntil },
+ {"nativeGetHandle", "(J)Landroid/os/IBinder;",
+ (void*)nativeGetHandle }
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5828829..561bcbc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2127,7 +2127,7 @@
<!-- Allows an application to grant specific permissions.
@hide -->
<permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|verifier" />
<!-- Allows an app that has this permission and the permissions to install packages
to request certain runtime permissions to be granted at installation.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 400c822..76f7062 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2130,8 +2130,10 @@
string that's stored in 8-bit unpacked format) characters.-->
<bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
- <!-- Package name providing WebView implementation. -->
- <string name="config_webViewPackageName" translatable="false">com.android.webview</string>
+ <!-- List of package names (ordered by preference) providing WebView implementations. -->
+ <string-array name="config_webViewPackageNames" translatable="false">
+ <item>com.android.webview</item>
+ </string-array>
<!-- If EMS is not supported, framework breaks down EMS into single segment SMS
and adds page info " x/y". This config is used to set which carrier doesn't
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index dd42c22..bfb0d10 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -367,11 +367,6 @@
<dimen name="resolver_max_width">480dp</dimen>
- <!-- @deprecated Use config_windowOutsetBottom instead.
- Size of the offset applied to the position of the circular mask. This
- is only used on circular displays. In the case where there is no
- "chin", this will default to 0 -->
- <dimen name="circular_display_mask_offset">0px</dimen>
<!-- Amount to reduce the size of the circular mask by (to compensate for
aliasing effects). This is only used on circular displays. -->
<dimen name="circular_display_mask_thickness">1px</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1932fc..ba28e81 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -445,7 +445,6 @@
<java-symbol type="dimen" name="notification_large_icon_circle_padding" />
<java-symbol type="dimen" name="notification_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
- <java-symbol type="dimen" name="circular_display_mask_offset" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
<java-symbol type="dimen" name="circular_display_mask_thickness" />
@@ -2019,7 +2018,7 @@
<java-symbol type="attr" name="actionModeWebSearchDrawable" />
<java-symbol type="string" name="websearch" />
<java-symbol type="drawable" name="ic_media_video_poster" />
- <java-symbol type="string" name="config_webViewPackageName" />
+ <java-symbol type="array" name="config_webViewPackageNames" />
<!-- From SubtitleView -->
<java-symbol type="dimen" name="subtitle_corner_radius" />
diff --git a/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
new file mode 100644
index 0000000..3638473
--- /dev/null
+++ b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.google.caliper.SimpleBenchmark;
+
+import org.xmlpull.v1.XmlPullParser;
+
+public class ResourcesBenchmark extends SimpleBenchmark {
+
+ private AssetManager mAsset;
+ private Resources mRes;
+
+ private int mTextId;
+ private int mColorId;
+ private int mIntegerId;
+ private int mLayoutId;
+
+ @Override
+ protected void setUp() {
+ mAsset = new AssetManager();
+ mAsset.addAssetPath("/system/framework/framework-res.apk");
+ mRes = new Resources(mAsset, null, null);
+
+ mTextId = mRes.getIdentifier("cancel", "string", "android");
+ mColorId = mRes.getIdentifier("transparent", "color", "android");
+ mIntegerId = mRes.getIdentifier("config_shortAnimTime", "integer", "android");
+ mLayoutId = mRes.getIdentifier("two_line_list_item", "layout", "android");
+ }
+
+ @Override
+ protected void tearDown() {
+ mAsset.close();
+ }
+
+ public void timeGetString(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getText(mTextId);
+ }
+ }
+
+ public void timeGetColor(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getColor(mColorId, null);
+ }
+ }
+
+ public void timeGetInteger(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getInteger(mIntegerId);
+ }
+ }
+
+ public void timeGetLayoutAndTraverse(int reps) throws Exception {
+ for (int i = 0; i < reps; i++) {
+ final XmlResourceParser parser = mRes.getLayout(mLayoutId);
+ try {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ // Walk the entire tree
+ }
+ } finally {
+ parser.close();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3c10368..6903b7b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -136,7 +136,9 @@
</intent-filter>
</activity>
- <activity android:name="android.widget.TextViewActivity" android:label="TextViewActivity">
+ <activity android:name="android.widget.TextViewActivity"
+ android:label="TextViewActivity"
+ android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 6a76a27..bb51570 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -36,7 +36,6 @@
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.OrientationUtil;
import android.view.KeyEvent;
/**
@@ -44,16 +43,13 @@
*/
public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
- private OrientationUtil mOrientationUtil;
-
public TextViewActivityTest() {
super(TextViewActivity.class);
}
@Override
public void setUp() {
- mOrientationUtil = OrientationUtil.initializeAndStartActivityIfNotStarted(this);
- mOrientationUtil.setPortraitOrientation();
+ getActivity();
}
@SmallTest
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 4f56965..4d2037b 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -812,8 +812,7 @@
setTileModeY(parseTileMode(tileModeY));
}
- final int densityDpi = r.getDisplayMetrics().densityDpi;
- state.mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.mTargetDensity = Drawable.resolveDensity(r, 0);
}
@Override
@@ -975,13 +974,7 @@
* after inflating or applying a theme.
*/
private void updateLocalState(Resources res) {
- if (res != null) {
- final int densityDpi = res.getDisplayMetrics().densityDpi;
- mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
- } else {
- mTargetDensity = mBitmapState.mTargetDensity;
- }
-
+ mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode);
computeBitmapSize();
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index ff28777..39d13df 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1079,8 +1079,7 @@
// to the compatibility density only to have them scaled back up when
// drawn to the screen.
if (opts == null) opts = new BitmapFactory.Options();
- opts.inScreenDensity = res != null
- ? res.getDisplayMetrics().noncompatDensityDpi : DisplayMetrics.DENSITY_DEVICE;
+ opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
@@ -1338,6 +1337,65 @@
}
/**
+ * Scales a floating-point pixel value from the source density to the
+ * target density.
+ *
+ * @param pixels the pixel value for use in source density
+ * @param sourceDensity the source density
+ * @param targetDensity the target density
+ * @return the scaled pixel value for use in target density
+ */
+ static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
+ return pixels * targetDensity / sourceDensity;
+ }
+
+ /**
+ * Scales a pixel value from the source density to the target density,
+ * optionally handling the resulting pixel value as a size rather than an
+ * offset.
+ * <p>
+ * A size conversion involves rounding the base value and ensuring that
+ * a non-zero base value is at least one pixel in size.
+ * <p>
+ * An offset conversion involves simply truncating the base value to an
+ * integer.
+ *
+ * @param pixels the pixel value for use in source density
+ * @param sourceDensity the source density
+ * @param targetDensity the target density
+ * @param isSize {@code true} to handle the resulting scaled value as a
+ * size, or {@code false} to handle it as an offset
+ * @return the scaled pixel value for use in target density
+ */
+ static int scaleFromDensity(
+ int pixels, int sourceDensity, int targetDensity, boolean isSize) {
+ if (pixels == 0 || sourceDensity == targetDensity) {
+ return pixels;
+ }
+
+ final float result = pixels * targetDensity / (float) sourceDensity;
+ if (!isSize) {
+ return (int) result;
+ }
+
+ final int rounded = Math.round(result);
+ if (rounded != 0) {
+ return rounded;
+ } else if (pixels == 0) {
+ return 0;
+ } else if (pixels > 0) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ static int resolveDensity(@NonNull Resources r, int parentDensity) {
+ final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
+ return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ }
+
+ /**
* Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
* attribute's enum value.
*
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index ac72734..b2044e1 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -701,13 +701,8 @@
DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
Resources res) {
mOwner = owner;
-
- final Resources sourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
- mSourceRes = sourceRes;
-
- final int densityDpi = sourceRes == null ? 0 : sourceRes.getDisplayMetrics().densityDpi;
- final int sourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
- mDensity = sourceDensity;
+ mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
+ mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
if (orig != null) {
mChangingConfigurations = orig.mChangingConfigurations;
@@ -731,7 +726,7 @@
mHasTintList = orig.mHasTintList;
mHasTintMode = orig.mHasTintMode;
- if (orig.mDensity == sourceDensity) {
+ if (orig.mDensity == mDensity) {
if (orig.mCheckedPadding) {
mConstantPadding = new Rect(orig.mConstantPadding);
mCheckedPadding = true;
@@ -903,13 +898,11 @@
// The density may have changed since the last update (if any). Any
// dimension-type attributes will need their default values scaled.
- final int densityDpi = res.getDisplayMetrics().densityDpi;
- final int newSourceDensity = densityDpi == 0 ?
- DisplayMetrics.DENSITY_DEFAULT : densityDpi;
- final int oldSourceDensity = mDensity;
- mDensity = newSourceDensity;
+ final int targetDensity = Drawable.resolveDensity(res, mDensity);
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
- if (oldSourceDensity != newSourceDensity) {
+ if (sourceDensity != targetDensity) {
mCheckedConstantSize = false;
mCheckedPadding = false;
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index d7fd8a5..4be86ef 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -17,11 +17,13 @@
package android.graphics.drawable;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -40,6 +42,7 @@
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
@@ -1136,10 +1139,14 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ mGradientState.setDensity(Drawable.resolveDensity(r, 0));
+
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
- super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible);
updateStateFromTypedArray(a);
a.recycle();
@@ -1149,7 +1156,7 @@
}
@Override
- public void applyTheme(Theme t) {
+ public void applyTheme(@NonNull Theme t) {
super.applyTheme(t);
final GradientState state = mGradientState;
@@ -1157,6 +1164,8 @@
return;
}
+ state.setDensity(Drawable.resolveDensity(t.getResources(), 0));
+
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
state.mThemeAttrs, R.styleable.GradientDrawable);
@@ -1668,7 +1677,7 @@
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mGradientState = new GradientState(mGradientState);
+ mGradientState = new GradientState(mGradientState, null);
updateLocalState(null);
mMutated = true;
}
@@ -1723,6 +1732,8 @@
ColorStateList mTint = null;
PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+ int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
int[] mThemeAttrs;
int[] mAttrSize;
int[] mAttrGradient;
@@ -1736,55 +1747,145 @@
setGradientColors(gradientColors);
}
- public GradientState(GradientState state) {
- mChangingConfigurations = state.mChangingConfigurations;
- mShape = state.mShape;
- mGradient = state.mGradient;
- mAngle = state.mAngle;
- mOrientation = state.mOrientation;
- mSolidColors = state.mSolidColors;
- if (state.mGradientColors != null) {
- mGradientColors = state.mGradientColors.clone();
+ public GradientState(@NonNull GradientState orig, @Nullable Resources res) {
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mShape = orig.mShape;
+ mGradient = orig.mGradient;
+ mAngle = orig.mAngle;
+ mOrientation = orig.mOrientation;
+ mSolidColors = orig.mSolidColors;
+ if (orig.mGradientColors != null) {
+ mGradientColors = orig.mGradientColors.clone();
}
- if (state.mPositions != null) {
- mPositions = state.mPositions.clone();
+ if (orig.mPositions != null) {
+ mPositions = orig.mPositions.clone();
}
- mStrokeColors = state.mStrokeColors;
- mStrokeWidth = state.mStrokeWidth;
- mStrokeDashWidth = state.mStrokeDashWidth;
- mStrokeDashGap = state.mStrokeDashGap;
- mRadius = state.mRadius;
- if (state.mRadiusArray != null) {
- mRadiusArray = state.mRadiusArray.clone();
+ mStrokeColors = orig.mStrokeColors;
+ mStrokeWidth = orig.mStrokeWidth;
+ mStrokeDashWidth = orig.mStrokeDashWidth;
+ mStrokeDashGap = orig.mStrokeDashGap;
+ mRadius = orig.mRadius;
+ if (orig.mRadiusArray != null) {
+ mRadiusArray = orig.mRadiusArray.clone();
}
- if (state.mPadding != null) {
- mPadding = new Rect(state.mPadding);
+ if (orig.mPadding != null) {
+ mPadding = new Rect(orig.mPadding);
}
- mWidth = state.mWidth;
- mHeight = state.mHeight;
- mInnerRadiusRatio = state.mInnerRadiusRatio;
- mThicknessRatio = state.mThicknessRatio;
- mInnerRadius = state.mInnerRadius;
- mThickness = state.mThickness;
- mDither = state.mDither;
- mOpticalInsets = state.mOpticalInsets;
- mCenterX = state.mCenterX;
- mCenterY = state.mCenterY;
- mGradientRadius = state.mGradientRadius;
- mGradientRadiusType = state.mGradientRadiusType;
- mUseLevel = state.mUseLevel;
- mUseLevelForShape = state.mUseLevelForShape;
- mOpaqueOverBounds = state.mOpaqueOverBounds;
- mOpaqueOverShape = state.mOpaqueOverShape;
- mTint = state.mTint;
- mTintMode = state.mTintMode;
- mThemeAttrs = state.mThemeAttrs;
- mAttrSize = state.mAttrSize;
- mAttrGradient = state.mAttrGradient;
- mAttrSolid = state.mAttrSolid;
- mAttrStroke = state.mAttrStroke;
- mAttrCorners = state.mAttrCorners;
- mAttrPadding = state.mAttrPadding;
+ mWidth = orig.mWidth;
+ mHeight = orig.mHeight;
+ mInnerRadiusRatio = orig.mInnerRadiusRatio;
+ mThicknessRatio = orig.mThicknessRatio;
+ mInnerRadius = orig.mInnerRadius;
+ mThickness = orig.mThickness;
+ mDither = orig.mDither;
+ mOpticalInsets = orig.mOpticalInsets;
+ mCenterX = orig.mCenterX;
+ mCenterY = orig.mCenterY;
+ mGradientRadius = orig.mGradientRadius;
+ mGradientRadiusType = orig.mGradientRadiusType;
+ mUseLevel = orig.mUseLevel;
+ mUseLevelForShape = orig.mUseLevelForShape;
+ mOpaqueOverBounds = orig.mOpaqueOverBounds;
+ mOpaqueOverShape = orig.mOpaqueOverShape;
+ mTint = orig.mTint;
+ mTintMode = orig.mTintMode;
+ mThemeAttrs = orig.mThemeAttrs;
+ mAttrSize = orig.mAttrSize;
+ mAttrGradient = orig.mAttrGradient;
+ mAttrSolid = orig.mAttrSolid;
+ mAttrStroke = orig.mAttrStroke;
+ mAttrCorners = orig.mAttrCorners;
+ mAttrPadding = orig.mAttrPadding;
+
+ mDensity = Drawable.resolveDensity(res, orig.mDensity);
+ if (orig.mDensity != mDensity) {
+ applyDensityScaling(orig.mDensity, mDensity);
+ }
+ }
+
+ /**
+ * Sets the constant state density.
+ * <p>
+ * If the density has been previously set, dispatches the change to
+ * subclasses so that density-dependent properties may be scaled as
+ * necessary.
+ *
+ * @param targetDensity the new constant state density
+ */
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
+
+ applyDensityScaling(sourceDensity, targetDensity);
+ }
+ }
+
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ if (mInnerRadius > 0) {
+ mInnerRadius = Drawable.scaleFromDensity(
+ mInnerRadius, sourceDensity, targetDensity, true);
+ }
+ if (mThickness > 0) {
+ mThickness = Drawable.scaleFromDensity(
+ mThickness, sourceDensity, targetDensity, true);
+ }
+ if (mOpticalInsets != Insets.NONE) {
+ final int left = Drawable.scaleFromDensity(
+ mOpticalInsets.left, sourceDensity, targetDensity, true);
+ final int top = Drawable.scaleFromDensity(
+ mOpticalInsets.top, sourceDensity, targetDensity, true);
+ final int right = Drawable.scaleFromDensity(
+ mOpticalInsets.right, sourceDensity, targetDensity, true);
+ final int bottom = Drawable.scaleFromDensity(
+ mOpticalInsets.bottom, sourceDensity, targetDensity, true);
+ mOpticalInsets = Insets.of(left, top, right, bottom);
+ }
+ if (mPadding != null) {
+ mPadding.left = Drawable.scaleFromDensity(
+ mPadding.left, sourceDensity, targetDensity, false);
+ mPadding.top = Drawable.scaleFromDensity(
+ mPadding.top, sourceDensity, targetDensity, false);
+ mPadding.right = Drawable.scaleFromDensity(
+ mPadding.right, sourceDensity, targetDensity, false);
+ mPadding.bottom = Drawable.scaleFromDensity(
+ mPadding.bottom, sourceDensity, targetDensity, false);
+ }
+ if (mRadius > 0) {
+ mRadius = Drawable.scaleFromDensity(mRadius, sourceDensity, targetDensity);
+ }
+ if (mRadiusArray != null) {
+ mRadiusArray[0] = Drawable.scaleFromDensity(
+ (int) mRadiusArray[0], sourceDensity, targetDensity, true);
+ mRadiusArray[1] = Drawable.scaleFromDensity(
+ (int) mRadiusArray[1], sourceDensity, targetDensity, true);
+ mRadiusArray[2] = Drawable.scaleFromDensity(
+ (int) mRadiusArray[2], sourceDensity, targetDensity, true);
+ mRadiusArray[3] = Drawable.scaleFromDensity(
+ (int) mRadiusArray[3], sourceDensity, targetDensity, true);
+ }
+ if (mStrokeWidth > 0) {
+ mStrokeWidth = Drawable.scaleFromDensity(
+ mStrokeWidth, sourceDensity, targetDensity, true);
+ }
+ if (mStrokeDashWidth > 0) {
+ mStrokeDashWidth = Drawable.scaleFromDensity(
+ mStrokeDashGap, sourceDensity, targetDensity);
+ }
+ if (mStrokeDashGap > 0) {
+ mStrokeDashGap = Drawable.scaleFromDensity(
+ mStrokeDashGap, sourceDensity, targetDensity);
+ }
+ if (mGradientRadiusType == RADIUS_TYPE_PIXELS) {
+ mGradientRadius = Drawable.scaleFromDensity(
+ mGradientRadius, sourceDensity, targetDensity);
+ }
+ if (mWidth > 0) {
+ mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
+ }
+ if (mHeight > 0) {
+ mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
+ }
}
@Override
@@ -1805,8 +1906,18 @@
}
@Override
- public Drawable newDrawable(Resources res) {
- return new GradientDrawable(this, res);
+ public Drawable newDrawable(@Nullable Resources res) {
+ // If this drawable is being created for a different density,
+ // just create a new constant state and call it a day.
+ final GradientState state;
+ final int density = Drawable.resolveDensity(res, mDensity);
+ if (density != mDensity) {
+ state = new GradientState(this, res);
+ } else {
+ state = this;
+ }
+
+ return new GradientDrawable(state, res);
}
@Override
@@ -1913,7 +2024,7 @@
*
* @param state Constant state from which the drawable inherits
*/
- private GradientDrawable(GradientState state, Resources res) {
+ private GradientDrawable(@NonNull GradientState state, @Nullable Resources res) {
mGradientState = state;
updateLocalState(res);
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 26232a9..44d7530 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -382,7 +382,9 @@
* @hide
*/
public void convertToAshmem() {
- if (mType == TYPE_BITMAP && getBitmap().isMutable()) {
+ if (mType == TYPE_BITMAP &&
+ getBitmap().isMutable() &&
+ getBitmap().getAllocationByteCount() >= (128 * (1 << 10))) {
setBitmap(getBitmap().createAshmemBitmap());
}
}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 4368fe9..1ede105 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -170,8 +170,7 @@
// The density may have changed since the last update. This will
// apply scaling to any existing constant state properties.
- final int densityDpi = r.getDisplayMetrics().densityDpi;
- final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ final int density = Drawable.resolveDensity(r, 0);
state.setDensity(density);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
@@ -200,8 +199,7 @@
return;
}
- final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
- final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ final int density = Drawable.resolveDensity(t.getResources(), 0);
state.setDensity(density);
if (state.mThemeAttrs != null) {
@@ -1774,7 +1772,9 @@
final Drawable clone;
if (dr != null) {
final ConstantState cs = dr.getConstantState();
- if (res != null) {
+ if (cs == null) {
+ clone = dr;
+ } else if (res != null) {
clone = cs.newDrawable(res);
} else {
clone = cs.newDrawable();
@@ -1800,9 +1800,7 @@
mGravity = orig.mGravity;
mId = orig.mId;
- final int densityDpi = res == null ? orig.mDensity : res.getDisplayMetrics().densityDpi;
- mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-
+ mDensity = Drawable.resolveDensity(res, orig.mDensity);
if (orig.mDensity != mDensity) {
applyDensityScaling(orig.mDensity, mDensity);
}
@@ -1823,21 +1821,21 @@
}
private void applyDensityScaling(int sourceDensity, int targetDensity) {
- mInsetL = Bitmap.scaleFromDensity(mInsetL, sourceDensity, targetDensity);
- mInsetT = Bitmap.scaleFromDensity(mInsetT, sourceDensity, targetDensity);
- mInsetR = Bitmap.scaleFromDensity(mInsetR, sourceDensity, targetDensity);
- mInsetB = Bitmap.scaleFromDensity(mInsetB, sourceDensity, targetDensity);
+ mInsetL = Drawable.scaleFromDensity(mInsetL, sourceDensity, targetDensity, false);
+ mInsetT = Drawable.scaleFromDensity(mInsetT, sourceDensity, targetDensity, false);
+ mInsetR = Drawable.scaleFromDensity(mInsetR, sourceDensity, targetDensity, false);
+ mInsetB = Drawable.scaleFromDensity(mInsetB, sourceDensity, targetDensity, false);
if (mInsetS != UNDEFINED_INSET) {
- mInsetS = Bitmap.scaleFromDensity(mInsetS, sourceDensity, targetDensity);
+ mInsetS = Drawable.scaleFromDensity(mInsetS, sourceDensity, targetDensity, false);
}
if (mInsetE != UNDEFINED_INSET) {
- mInsetE = Bitmap.scaleFromDensity(mInsetE, sourceDensity, targetDensity);
+ mInsetE = Drawable.scaleFromDensity(mInsetE, sourceDensity, targetDensity, false);
}
if (mWidth > 0) {
- mWidth = Bitmap.scaleFromDensity(mWidth, sourceDensity, targetDensity);
+ mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
}
if (mHeight > 0) {
- mHeight = Bitmap.scaleFromDensity(mHeight, sourceDensity, targetDensity);
+ mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
}
}
}
@@ -1874,16 +1872,7 @@
LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
@Nullable Resources res) {
- final int densityDpi;
- if (res != null) {
- densityDpi = res.getDisplayMetrics().densityDpi;
- } else if (orig != null) {
- densityDpi = orig.mDensity;
- } else {
- densityDpi = 0;
- }
-
- mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
if (orig != null) {
final ChildDrawable[] origChildDrawable = orig.mChildren;
@@ -1939,28 +1928,28 @@
private void applyDensityScaling(int sourceDensity, int targetDensity) {
if (mPaddingLeft > 0) {
- mPaddingLeft = Bitmap.scaleFromDensity(
- mPaddingLeft, sourceDensity, targetDensity);
+ mPaddingLeft = Drawable.scaleFromDensity(
+ mPaddingLeft, sourceDensity, targetDensity, false);
}
if (mPaddingTop > 0) {
- mPaddingTop = Bitmap.scaleFromDensity(
- mPaddingTop, sourceDensity, targetDensity);
+ mPaddingTop = Drawable.scaleFromDensity(
+ mPaddingTop, sourceDensity, targetDensity, false);
}
if (mPaddingRight > 0) {
- mPaddingRight = Bitmap.scaleFromDensity(
- mPaddingRight, sourceDensity, targetDensity);
+ mPaddingRight = Drawable.scaleFromDensity(
+ mPaddingRight, sourceDensity, targetDensity, false);
}
if (mPaddingBottom > 0) {
- mPaddingBottom = Bitmap.scaleFromDensity(
- mPaddingBottom, sourceDensity, targetDensity);
+ mPaddingBottom = Drawable.scaleFromDensity(
+ mPaddingBottom, sourceDensity, targetDensity, false);
}
if (mPaddingStart > 0) {
- mPaddingStart = Bitmap.scaleFromDensity(
- mPaddingStart, sourceDensity, targetDensity);
+ mPaddingStart = Drawable.scaleFromDensity(
+ mPaddingStart, sourceDensity, targetDensity, false);
}
if (mPaddingEnd > 0) {
- mPaddingEnd = Bitmap.scaleFromDensity(
- mPaddingEnd, sourceDensity, targetDensity);
+ mPaddingEnd = Drawable.scaleFromDensity(
+ mPaddingEnd, sourceDensity, targetDensity, false);
}
}
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 231405d..4d51d63 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -181,10 +181,10 @@
}
private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
- int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity);
- int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity);
- int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity);
- int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity);
+ int left = Drawable.scaleFromDensity(insets.left, sdensity, tdensity, true);
+ int top = Drawable.scaleFromDensity(insets.top, sdensity, tdensity, true);
+ int right = Drawable.scaleFromDensity(insets.right, sdensity, tdensity, true);
+ int bottom = Drawable.scaleFromDensity(insets.bottom, sdensity, tdensity, true);
return Insets.of(left, top, right, bottom);
}
@@ -196,18 +196,20 @@
mBitmapHeight = mNinePatch.getHeight();
mOpticalInsets = mNinePatchState.mOpticalInsets;
} else {
- mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity);
- mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), sdensity, tdensity);
+ mBitmapWidth = Drawable.scaleFromDensity(
+ mNinePatch.getWidth(), sdensity, tdensity, true);
+ mBitmapHeight = Drawable.scaleFromDensity(
+ mNinePatch.getHeight(), sdensity, tdensity, true);
if (mNinePatchState.mPadding != null && mPadding != null) {
Rect dest = mPadding;
Rect src = mNinePatchState.mPadding;
if (dest == src) {
mPadding = dest = new Rect(src);
}
- dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
- dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
- dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
- dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
+ dest.left = Drawable.scaleFromDensity(src.left, sdensity, tdensity, true);
+ dest.top = Drawable.scaleFromDensity(src.top, sdensity, tdensity, true);
+ dest.right = Drawable.scaleFromDensity(src.right, sdensity, tdensity, true);
+ dest.bottom = Drawable.scaleFromDensity(src.bottom, sdensity, tdensity, true);
}
mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity);
}
@@ -490,8 +492,7 @@
state.mTint = tint;
}
- final int densityDpi = r.getDisplayMetrics().densityDpi;
- state.mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.mTargetDensity = Drawable.resolveDensity(r, state.mTargetDensity);
}
@Override
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index a196fad..52e7f24 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -1008,7 +1008,8 @@
private void applyDensityScaling(int sourceDensity, int targetDensity) {
if (mMaxRadius != RADIUS_AUTO) {
- mMaxRadius = Bitmap.scaleFromDensity(mMaxRadius, sourceDensity, targetDensity);
+ mMaxRadius = Drawable.scaleFromDensity(
+ mMaxRadius, sourceDensity, targetDensity, true);
}
}
@@ -1039,16 +1040,13 @@
private RippleDrawable(RippleState state, Resources res) {
mState = new RippleState(state, this, res);
mLayerState = mState;
+ mDensity = Drawable.resolveDensity(res, mState.mDensity);
if (mState.mNum > 0) {
ensurePadding();
refreshPadding();
}
- if (res != null) {
- mDensity = res.getDisplayMetrics().density;
- }
-
updateLocalState();
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index e161f3d..eee9b24 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -259,13 +259,7 @@
* displayed, or {@code null} to use the constant state defaults
*/
private void updateLocalState(Resources res) {
- if (res != null) {
- final int densityDpi = res.getDisplayMetrics().densityDpi;
- mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
- } else {
- mTargetDensity = mVectorState.mVPathRenderer.mSourceDensity;
- }
-
+ mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity);
mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
computeVectorSize();
}
@@ -453,18 +447,18 @@
final int sourceDensity = pathRenderer.mSourceDensity;
final int targetDensity = mTargetDensity;
if (targetDensity != sourceDensity) {
- mDpiScaledWidth = Bitmap.scaleFromDensity(
- (int) pathRenderer.mBaseWidth, sourceDensity, targetDensity);
- mDpiScaledHeight = Bitmap.scaleFromDensity(
- (int) pathRenderer.mBaseHeight,sourceDensity, targetDensity);
- final int left = Bitmap.scaleFromDensity(
- opticalInsets.left, sourceDensity, targetDensity);
- final int right = Bitmap.scaleFromDensity(
- opticalInsets.right, sourceDensity, targetDensity);
- final int top = Bitmap.scaleFromDensity(
- opticalInsets.top, sourceDensity, targetDensity);
- final int bottom = Bitmap.scaleFromDensity(
- opticalInsets.bottom, sourceDensity, targetDensity);
+ mDpiScaledWidth = Drawable.scaleFromDensity(
+ (int) pathRenderer.mBaseWidth, sourceDensity, targetDensity, true);
+ mDpiScaledHeight = Drawable.scaleFromDensity(
+ (int) pathRenderer.mBaseHeight,sourceDensity, targetDensity, true);
+ final int left = Drawable.scaleFromDensity(
+ opticalInsets.left, sourceDensity, targetDensity, false);
+ final int right = Drawable.scaleFromDensity(
+ opticalInsets.right, sourceDensity, targetDensity, false);
+ final int top = Drawable.scaleFromDensity(
+ opticalInsets.top, sourceDensity, targetDensity, false);
+ final int bottom = Drawable.scaleFromDensity(
+ opticalInsets.bottom, sourceDensity, targetDensity, false);
mDpiScaledInsets = Insets.of(left, top, right, bottom);
} else {
mDpiScaledWidth = (int) pathRenderer.mBaseWidth;
@@ -600,11 +594,10 @@
// The density may have changed since the last update (if any). Any
// dimension-type attributes will need their default values scaled.
- final int densityDpi = a.getResources().getDisplayMetrics().densityDpi;
- final int newSourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
- final int oldSourceDensity = pathRenderer.mSourceDensity;
- final float densityScale = newSourceDensity / (float) oldSourceDensity;
- pathRenderer.mSourceDensity = newSourceDensity;
+ final int targetDensity = Drawable.resolveDensity(a.getResources(), 0);
+ final int sourceDensity = pathRenderer.mSourceDensity;
+ final float densityScale = targetDensity / (float) sourceDensity;
+ pathRenderer.mSourceDensity = targetDensity;
final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
if (tintMode != -1) {
@@ -647,16 +640,16 @@
"<vector> tag requires height > 0");
}
- final int insetLeft = a.getDimensionPixelSize(
+ final int insetLeft = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetLeft,
(int) (pathRenderer.mOpticalInsets.left * densityScale));
- final int insetTop = a.getDimensionPixelSize(
+ final int insetTop = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetTop,
(int) (pathRenderer.mOpticalInsets.top * densityScale));
- final int insetRight = a.getDimensionPixelSize(
+ final int insetRight = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetRight,
(int) (pathRenderer.mOpticalInsets.right * densityScale));
- final int insetBottom = a.getDimensionPixelSize(
+ final int insetBottom = a.getDimensionPixelOffset(
R.styleable.VectorDrawable_opticalInsetBottom,
(int) (pathRenderer.mOpticalInsets.bottom * densityScale));
pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index d94c91d..ae5fa6c 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -56,6 +56,7 @@
Layer.cpp \
LayerCache.cpp \
LayerRenderer.cpp \
+ LayerUpdateQueue.cpp \
Matrix.cpp \
OpenGLRenderer.cpp \
Patch.cpp \
@@ -204,6 +205,7 @@
unit_tests/ClipAreaTests.cpp \
unit_tests/DamageAccumulatorTests.cpp \
unit_tests/FatVectorTests.cpp \
+ unit_tests/LayerUpdateQueueTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/StringUtilsTests.cpp
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 0868853..2fca5ea 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -20,6 +20,7 @@
#include "Glop.h"
#include "GlopBuilder.h"
#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
#include "utils/GLUtils.h"
namespace android {
@@ -29,10 +30,13 @@
// OffscreenBuffer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t textureWidth, uint32_t textureHeight,
uint32_t viewportWidth, uint32_t viewportHeight)
- : texture(caches)
- , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
+ : renderState(renderState)
+ , viewportWidth(viewportWidth)
+ , viewportHeight(viewportHeight)
+ , texture(caches) {
texture.width = textureWidth;
texture.height = textureHeight;
@@ -48,16 +52,72 @@
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
+void OffscreenBuffer::updateMeshFromRegion() {
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform
+ Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+ size_t count;
+ const android::Rect* rects = safeRegion.getArray(&count);
+
+ const float texX = 1.0f / float(viewportWidth);
+ const float texY = 1.0f / float(viewportHeight);
+
+ FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+ TextureVertex* mesh = &meshVector[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (viewportHeight - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (viewportHeight - r->bottom) * texY;
+
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ elementCount = count * 6;
+ renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+ sizeof(TextureVertex) * count * 4,
+ &meshVector[0],
+ GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+ texture.deleteTexture();
+ renderState.meshState().deleteMeshBuffer(vbo);
+ elementCount = 0;
+ vbo = 0;
+}
+
////////////////////////////////////////////////////////////////////////////////
// BakedOpRenderer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
+OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
+ uint32_t width, uint32_t height) {
+ // TODO: get from cache!
+ return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
+}
+
+void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
+ // TODO: return texture/offscreenbuffer to cache!
+ delete offscreenBuffer;
+}
+
+OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
- // TODO: really should be caching these!
- OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
- mRenderTarget.offscreenBuffer = buffer;
+ OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
+ startLayer(buffer);
+ return buffer;
+}
+
+void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+ mRenderTarget.offscreenBuffer = offscreenBuffer;
// create and bind framebuffer
mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
@@ -65,7 +125,7 @@
// attach the texture to the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- buffer->texture.id, 0);
+ offscreenBuffer->texture.id, 0);
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
"framebuffer incomplete!");
@@ -75,11 +135,11 @@
glClear(GL_COLOR_BUFFER_BIT);
// Change the viewport & ortho projection
- setViewport(width, height);
- return buffer;
+ setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
}
void BakedOpRenderer::endLayer() {
+ mRenderTarget.offscreenBuffer->updateMeshFromRegion();
mRenderTarget.offscreenBuffer = nullptr;
// Detach the texture from the FBO
@@ -144,6 +204,12 @@
mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
clip.getWidth(), clip.getHeight());
}
+ if (mRenderTarget.offscreenBuffer) { // TODO: not with multi-draw
+ // register layer damage to draw-back region
+ const Rect& uiDirty = state.computedState.clippedBounds;
+ android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom);
+ mRenderTarget.offscreenBuffer->region.orSelf(dirty);
+ }
mRenderState.render(glop, mRenderTarget.orthoMatrix);
mHasDrawn = true;
}
@@ -156,6 +222,14 @@
LOG_ALWAYS_FATAL("unsupported operation");
}
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
Texture* texture = renderer.getTexture(op.bitmap);
@@ -199,36 +273,26 @@
renderer.renderGlop(state, glop);
}
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
OffscreenBuffer* buffer = *op.layerHandle;
// TODO: extend this to handle HW layers & paint properties which
// reside in node.properties().layerProperties()
- float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
- const bool tryToSnap = state.computedState.transform.isPureTranslate();
+ float layerAlpha = op.alpha * state.alpha;
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
- .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
.setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
.build();
renderer.renderGlop(state, glop);
- // destroy and delete, since each clipped saveLayer is only drawn once.
- buffer->texture.deleteTexture();
-
- // TODO: return texture/offscreenbuffer to cache!
- delete buffer;
+ if (op.destroy) {
+ BakedOpRenderer::destroyOffscreenBuffer(buffer);
+ }
}
} // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 16afad4..aa1e67d 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -35,12 +35,24 @@
*/
class OffscreenBuffer {
public:
- OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+ OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t textureWidth, uint32_t textureHeight,
uint32_t viewportWidth, uint32_t viewportHeight);
+ ~OffscreenBuffer();
+ // must be called prior to rendering, to construct/update vertex buffer
+ void updateMeshFromRegion();
+
+ RenderState& renderState;
+ uint32_t viewportWidth;
+ uint32_t viewportHeight;
Texture texture;
- Rect texCoords;
+
+ // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
+ // drawing back to screen / parent FBO.
Region region;
+ GLsizei elementCount = 0;
+ GLuint vbo = 0;
};
/**
@@ -60,12 +72,17 @@
, mOpaque(opaque) {
}
+ static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
+ uint32_t width, uint32_t height);
+ static void destroyOffscreenBuffer(OffscreenBuffer*);
+
RenderState& renderState() { return mRenderState; }
Caches& caches() { return mCaches; }
void startFrame(uint32_t width, uint32_t height);
void endFrame();
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
+ void startLayer(OffscreenBuffer* offscreenBuffer);
void endLayer();
Texture* getTexture(const SkBitmap* bitmap);
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 7c63e31..94a11f1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -23,6 +23,7 @@
#include "ShadowTessellator.h"
#include "utils/GLUtils.h"
+#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 61e958d..330dc29 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -43,7 +43,6 @@
#include <GLES3/gl3.h>
#include <utils/KeyedVector.h>
-#include <utils/Singleton.h>
#include <cutils/compiler.h>
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 86796c5..00c4e2d 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -154,7 +154,11 @@
return allocator.usedSize();
}
bool isEmpty() {
+#if HWUI_NEW_OPS
+ return ops.empty();
+#else
return !hasDrawOps;
+#endif
}
private:
@@ -179,7 +183,7 @@
// List of functors
LsaVector<Functor*> functors;
- bool hasDrawOps;
+ bool hasDrawOps; // only used if !HWUI_NEW_OPS
void cleanupResources();
};
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 06c8a21..6dd29ad 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -20,6 +20,7 @@
#include "Properties.h"
#include "utils/StringUtils.h"
+#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <utils/Log.h>
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 0a30d16..6689b88 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -19,12 +19,6 @@
#include <cutils/compiler.h>
-#include <utils/Singleton.h>
-#include <utils/SortedVector.h>
-#include <utils/String8.h>
-
-#include <GLES2/gl2.h>
-
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index d2da851..f3ac93b 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -70,6 +70,20 @@
// Mesh
////////////////////////////////////////////////////////////////////////////////
+GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) {
+ TRIGGER_STAGE(kMeshStage);
+
+ mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
+ mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
+ mOutGlop->mesh.vertices = {
+ vbo,
+ VertexAttribFlags::TextureCoord,
+ nullptr, nullptr, nullptr,
+ kTextureVertexStride };
+ mOutGlop->mesh.elementCount = elementCount;
+ return *this;
+}
+
GlopBuilder& GlopBuilder::setMeshUnitQuad() {
TRIGGER_STAGE(kMeshStage);
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 6f5802e..6270dcb 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -47,6 +47,7 @@
public:
GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop);
+ GlopBuilder& setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount);
GlopBuilder& setMeshUnitQuad();
GlopBuilder& setMeshTexturedUnitQuad(const UvMapper* uvMapper);
GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs);
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index aa105f9..8c46450 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -21,6 +21,8 @@
#include "GradientCache.h"
#include "Properties.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp
new file mode 100644
index 0000000..db5f676
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "LayerUpdateQueue.h"
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void LayerUpdateQueue::clear() {
+ mEntries.clear();
+}
+
+void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
+ damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
+ if (!damage.isEmpty()) {
+ for (Entry& entry : mEntries) {
+ if (CC_UNLIKELY(entry.renderNode == renderNode)) {
+ entry.damage.unionWith(damage);
+ return;
+ }
+ }
+ mEntries.emplace_back(renderNode, damage);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h
new file mode 100644
index 0000000..be612d2
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+
+#include "Rect.h"
+#include "utils/Macros.h"
+
+#include <vector>
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+
+class LayerUpdateQueue {
+ PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue);
+public:
+ struct Entry {
+ Entry(RenderNode* renderNode, const Rect& damage)
+ : renderNode(renderNode)
+ , damage(damage) {}
+ RenderNode* renderNode;
+ Rect damage;
+ };
+
+ LayerUpdateQueue() {}
+ void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty);
+ void clear();
+ const std::vector<Entry> entries() const { return mEntries; }
+private:
+ std::vector<Entry> mEntries;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index ddeb336..163f7cc 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
#include "utils/PaintUtils.h"
#include "RenderNode.h"
+#include "LayerUpdateQueue.h"
#include "SkCanvas.h"
#include "utils/Trace.h"
@@ -202,6 +203,14 @@
Rect mClipRect;
};
+OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+ : width(width)
+ , height(height)
+ , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
+ , beginLayerOp(beginLayerOp)
+ , renderNode(renderNode) {}
+
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -288,33 +297,48 @@
}
void OpReorderer::LayerReorderer::dump() const {
+ ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
+ this, width, height, offscreenBuffer, beginLayerOp, renderNode);
for (const BatchBase* batch : mBatches) {
batch->dump();
}
}
-OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
-
mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
- mLayerStack.push_back(0);
+ mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Vector3());
+
+ // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
+ // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
+ for (int i = layers.entries().size() - 1; i >= 0; i--) {
+ RenderNode* layerNode = layers.entries()[i].renderNode;
+ const Rect& layerDamage = layers.entries()[i].damage;
+
+ saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+ mCanvasState.writableSnapshot()->setClip(
+ layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
+
+ if (layerNode->getDisplayList()) {
+ deferImpl(*(layerNode->getDisplayList()));
+ }
+ restoreForLayer();
+ }
+
+ // Defer Fbo0
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
- // TODO: dedupe this code with onRenderNode()
- mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
- if (node->applyViewProperties(mCanvasState)) {
- // not rejected do ops...
- const DisplayList& displayList = node->getDisplayList();
- deferImpl(displayList);
- }
- mCanvasState.restore();
+ int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ deferNodePropsAndOps(*node);
+ mCanvasState.restoreToCount(count);
}
}
@@ -334,6 +358,23 @@
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
+ if (node.applyViewProperties(mCanvasState)) {
+ // not rejected so render
+ if (node.getLayer()) {
+ // HW layer
+ LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
+ BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+ if (bakedOpState) {
+ // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+ }
+ } else {
+ deferImpl(*(node.getDisplayList()));
+ }
+ }
+}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
@@ -365,11 +406,9 @@
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
- // apply RenderProperties state
- if (op.renderNode->applyViewProperties(mCanvasState)) {
- // if node not rejected based on properties, do ops...
- deferImpl(op.renderNode->getDisplayList());
- }
+ // then apply state from node properties, and defer ops
+ deferNodePropsAndOps(*op.renderNode);
+
mCanvasState.restoreToCount(count);
}
@@ -400,10 +439,8 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
-// TODO: test rejection at defer time, where the bounds become empty
-void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
- const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
- const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
mCanvasState.writableSnapshot()->transform->loadIdentity();
@@ -412,18 +449,27 @@
// create a new layer, and push its index on the stack
mLayerStack.push_back(mLayerReorderers.size());
- mLayerReorderers.emplace_back(layerWidth, layerHeight);
- mLayerReorderers.back().beginLayerOp = &op;
+ mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+}
+
+void OpReorderer::restoreForLayer() {
+ // restore canvas, and pop finished layer off of the stack
+ mCanvasState.restore();
+ mLayerStack.pop_back();
+}
+
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+ const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+ const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+ saveForLayer(layerWidth, layerHeight, &op, nullptr);
}
void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
- mCanvasState.restore();
-
const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
-
- // pop finished layer off of the stack
int finishedLayerIndex = mLayerStack.back();
- mLayerStack.pop_back();
+
+ restoreForLayer();
// record the draw operation into the previous layer's list of draw commands
// uses state from the associated beginLayerOp, since it has all the state needed for drawing
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 927ecfa..77be402 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -32,6 +32,7 @@
class BakedOpState;
class BatchBase;
+class LayerUpdateQueue;
class MergingOpBatch;
class OffscreenBuffer;
class OpBatch;
@@ -64,9 +65,14 @@
*/
class LayerReorderer {
public:
+ // Create LayerReorderer for Fbo0
LayerReorderer(uint32_t width, uint32_t height)
- : width(width)
- , height(height) {}
+ : LayerReorderer(width, height, nullptr, nullptr) {};
+
+ // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
+ // saveLayer, renderNode is present for a HW layer.
+ LayerReorderer(uint32_t width, uint32_t height,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
@@ -92,12 +98,12 @@
void dump() const;
- OffscreenBuffer* offscreenBuffer = nullptr;
- const BeginLayerOp* beginLayerOp = nullptr;
const uint32_t width;
const uint32_t height;
+ OffscreenBuffer* offscreenBuffer;
+ const BeginLayerOp* beginLayerOp;
+ const RenderNode* renderNode;
private:
-
std::vector<BatchBase*> mBatches;
/**
@@ -112,8 +118,8 @@
};
public:
- // TODO: not final, just presented this way for simplicity. Layers too?
- OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+ OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes);
OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
@@ -144,8 +150,13 @@
// later in the list will be drawn by earlier ones
for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
LayerReorderer& layer = mLayerReorderers[i];
- if (!layer.empty()) {
- layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
+ if (layer.renderNode) {
+ // cached HW layer - can't skip layer if empty
+ renderer.startLayer(layer.offscreenBuffer);
+ layer.replayBakedOpsImpl((void*)&renderer, receivers);
+ renderer.endLayer();
+ } else if (!layer.empty()) { // save layer - skip entire layer if empty
+ layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
layer.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endLayer();
}
@@ -171,12 +182,19 @@
virtual GLuint getTargetFbo() const override { return 0; }
private:
+ void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+ void restoreForLayer();
+
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
+ // should always be surrounded by a save/restore pair
+ void deferNodePropsAndOps(RenderNode& node);
+
void deferImpl(const DisplayList& displayList);
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 4031f2e1..fd9ab18 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -30,6 +30,8 @@
#include "thread/Signal.h"
#include "thread/TaskProcessor.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c0c61db..e818186 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,8 +17,12 @@
#include "Debug.h"
-#include <algorithm>
+#include <cutils/compiler.h>
#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <algorithm>
+#include <cstdlib>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 74cd74b..1293c78 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -18,8 +18,6 @@
#define ANDROID_HWUI_PROPERTIES_H
#include <cutils/properties.h>
-#include <stdlib.h>
-#include <utils/Singleton.h>
/**
* This file contains the list of system properties used to configure
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 7874d85..9ae868a 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -20,6 +20,7 @@
#include "utils/LinearAllocator.h"
#include "Rect.h"
#include "Matrix.h"
+#include "RenderNode.h"
#include "SkXfermode.h"
@@ -136,13 +137,42 @@
: RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
};
+/**
+ * Draws an OffscreenBuffer.
+ *
+ * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated,
+ * when creating/tracking a SkPaint* during defer isn't worth the bother.
+ */
struct LayerOp : RecordedOp {
+ // Records a one-use (saveLayer) layer for drawing. Once drawn, the layer will be destroyed.
LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
- : SUPER(LayerOp)
- , layerHandle(layerHandle) {}
+ : SUPER_PAINTLESS(LayerOp)
+ , layerHandle(layerHandle)
+ , alpha(paint->getAlpha() / 255.0f)
+ , mode(PaintUtils::getXfermodeDirect(paint))
+ , colorFilter(paint->getColorFilter())
+ , destroy(true) {}
+
+ LayerOp(RenderNode& node)
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
+ , layerHandle(node.getLayerHandle())
+ , alpha(node.properties().layerProperties().alpha() / 255.0f)
+ , mode(node.properties().layerProperties().xferMode())
+ , colorFilter(node.properties().layerProperties().colorFilter())
+ , destroy(false) {}
+
// Records a handle to the Layer object, since the Layer itself won't be
// constructed until after this operation is constructed.
OffscreenBuffer** layerHandle;
+ const float alpha;
+ const SkXfermode::Mode mode;
+
+ // pointer to object owned by either LayerProperties, or a recorded Paint object in a
+ // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
+ SkColorFilter* colorFilter;
+
+ // whether to destroy the layer, once rendered
+ const bool destroy;
};
}; // namespace uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 273af3a..7c460b1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -77,7 +77,6 @@
// ----------------------------------------------------------------------------
void RecordingCanvas::onViewportInitialized() {
-
}
void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 454ee24..8a56475 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -26,9 +26,10 @@
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
-#include "SkDrawFilter.h"
-#include "SkPaint.h"
-#include "SkTLazy.h"
+#include <SkDrawFilter.h>
+#include <SkPaint.h>
+#include <SkTLazy.h>
+
#include <vector>
namespace android {
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 8beed25..11d7a6a 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-#include <utils/Log.h>
-
#include "Debug.h"
#include "Properties.h"
#include "RenderBufferCache.h"
+#include <utils/Log.h>
+
+#include <cstdlib>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 39cb8e9..0601944 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -20,6 +20,7 @@
#include "Debug.h"
#if HWUI_NEW_OPS
#include "RecordedOp.h"
+#include "BakedOpRenderer.h"
#endif
#include "DisplayListOp.h"
#include "LayerRenderer.h"
@@ -42,11 +43,15 @@
namespace uirenderer {
void RenderNode::debugDumpLayers(const char* prefix) {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("TODO: dump layer");
+#else
if (mLayer) {
ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)",
prefix, this, getName(), mLayer, mLayer->getFbo(),
mLayer->wasBuildLayered ? "true" : "false");
}
+#endif
if (mDisplayList) {
for (auto&& child : mDisplayList->getChildren()) {
child->renderNode->debugDumpLayers(prefix);
@@ -60,18 +65,21 @@
, mDisplayList(nullptr)
, mStagingDisplayList(nullptr)
, mAnimatorManager(*this)
- , mLayer(nullptr)
, mParentCount(0) {
}
RenderNode::~RenderNode() {
deleteDisplayList();
delete mStagingDisplayList;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
+#else
if (mLayer) {
ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
mLayer->postDecStrong();
mLayer = nullptr;
}
+#endif
}
void RenderNode::setStagingDisplayList(DisplayList* displayList) {
@@ -240,13 +248,29 @@
}
}
+layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+#if HWUI_NEW_OPS
+ return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+#else
+ return LayerRenderer::createRenderLayer(renderState, width, height);
+#endif
+}
+
+void destroyLayer(layer_t* layer) {
+#if HWUI_NEW_OPS
+ BakedOpRenderer::destroyOffscreenBuffer(layer);
+#else
+ LayerRenderer::destroyLayer(layer);
+#endif
+}
+
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
if (CC_UNLIKELY(mLayer)) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
return;
@@ -254,14 +278,18 @@
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = LayerRenderer::createRenderLayer(
- info.canvasContext.getRenderState(), getWidth(), getHeight());
- applyLayerPropertiesToLayer(info);
- damageSelf(info);
- transformUpdateNeeded = true;
+ mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+ damageSelf(info);
+ transformUpdateNeeded = true;
+#if HWUI_NEW_OPS
+ } else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) {
+ // TODO: allow it to grow larger
+ if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) {
+#else
} else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
- LayerRenderer::destroyLayer(mLayer);
+#endif
+ destroyLayer(mLayer);
mLayer = nullptr;
}
damageSelf(info);
@@ -276,7 +304,7 @@
if (info.errorHandler) {
std::ostringstream err;
err << "Unable to create layer for " << getName();
- const int maxTextureSize = Caches::getInstance().maxTextureSize;
+ const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize;
if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
err << ", size " << getWidth() << "x" << getHeight()
<< " exceeds max size " << maxTextureSize;
@@ -292,9 +320,16 @@
// update the transform in window of the layer to reset its origin wrt light source position
Matrix4 windowTransform;
info.damageAccumulator->computeCurrentTransform(&windowTransform);
+#if HWUI_NEW_OPS
+ // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
+#else
mLayer->setWindowTransform(windowTransform);
+#endif
}
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
+#else
if (dirty.intersect(0, 0, getWidth(), getHeight())) {
dirty.roundOut(&dirty);
mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
@@ -304,6 +339,7 @@
if (info.renderer && mLayer->deferredUpdateScheduled) {
info.renderer->pushLayerUpdate(mLayer);
}
+#endif
// There might be prefetched layers that need to be accounted for.
// That might be us, so tell CanvasContext that this layer is in the
@@ -365,7 +401,9 @@
damageSelf(info);
info.damageAccumulator->popTransform();
syncProperties();
+#if !HWUI_NEW_OPS
applyLayerPropertiesToLayer(info);
+#endif
// We could try to be clever and only re-damage if the matrix changed.
// However, we don't need to worry about that. The cost of over-damaging
// here is only going to be a single additional map rect of this node
@@ -376,6 +414,7 @@
}
}
+#if !HWUI_NEW_OPS
void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
if (CC_LIKELY(!mLayer)) return;
@@ -384,6 +423,7 @@
mLayer->setColorFilter(props.colorFilter());
mLayer->setBlend(props.needsBlending());
}
+#endif
void RenderNode::syncDisplayList() {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
@@ -451,7 +491,7 @@
void RenderNode::destroyHardwareResources() {
if (mLayer) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
if (mDisplayList) {
@@ -978,7 +1018,11 @@
return;
}
+#if HWUI_NEW_OPS
+ const bool drawLayer = false;
+#else
const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
+#endif
// If we are updating the contents of mLayer, we don't want to apply any of
// the RenderNode's properties to this issueOperations pass. Those will all
// be applied when the layer is drawn, aka when this is true.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 57e41c6..3500cb2 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -44,13 +44,22 @@
namespace uirenderer {
class CanvasState;
-class DisplayListOp;
class DisplayListCanvas;
+class DisplayListOp;
class OpenGLRenderer;
+class OpReorderer;
class Rect;
-class Layer;
class SkiaShader;
+
+#if HWUI_NEW_OPS
+class OffscreenBuffer;
+typedef OffscreenBuffer layer_t;
+#else
+class Layer;
+typedef Layer layer_t;
+#endif
+
class ClipRectOp;
class SaveLayerOp;
class SaveOp;
@@ -162,11 +171,11 @@
return mStagingProperties;
}
- int getWidth() {
+ uint32_t getWidth() {
return properties().getWidth();
}
- int getHeight() {
+ uint32_t getHeight() {
return properties().getHeight();
}
@@ -193,9 +202,13 @@
}
// Only call if RenderNode has DisplayList...
- const DisplayList& getDisplayList() const {
- return *mDisplayList;
+ const DisplayList* getDisplayList() const {
+ return mDisplayList;
}
+#if HWUI_NEW_OPS
+ OffscreenBuffer* getLayer() const { return mLayer; }
+ OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
+#endif
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -262,7 +275,9 @@
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
+#if !HWUI_NEW_OPS
void applyLayerPropertiesToLayer(TreeInfo& info);
+#endif
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
void deleteDisplayList();
@@ -287,7 +302,7 @@
// Owned by RT. Lifecycle is managed by prepareTree(), with the exception
// being in ~RenderNode() which may happen on any thread.
- Layer* mLayer;
+ layer_t* mLayer = nullptr;
/**
* Draw time state - these properties are only set and used during rendering
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 1c31487..be25516 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,11 +16,11 @@
#ifndef TREEINFO_H
#define TREEINFO_H
-#include <string>
+#include "utils/Macros.h"
#include <utils/Timers.h>
-#include "utils/Macros.h"
+#include <string>
namespace android {
namespace uirenderer {
@@ -30,6 +30,7 @@
}
class DamageAccumulator;
+class LayerUpdateQueue;
class OpenGLRenderer;
class RenderState;
@@ -75,9 +76,14 @@
// Must not be null during actual usage
DamageAccumulator* damageAccumulator = nullptr;
+
+#if HWUI_NEW_OPS
+ LayerUpdateQueue* layerUpdateQueue = nullptr;
+#else
// The renderer that will be drawing the next frame. Use this to push any
// layer updates or similar. May be NULL.
OpenGLRenderer* renderer = nullptr;
+#endif
ErrorHandler* errorHandler = nullptr;
struct Out {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 43f170f..7b8d0e5 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -56,7 +56,9 @@
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
void BM_OpReorderer_deferAndRender::Run(int iters) {
- TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
+ TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
+ RenderState& renderState = thread.renderState();
+ Caches& caches = Caches::getInstance();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
OpReorderer reorderer(200, 200, *sReorderingDisplayList);
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 0521f65..03cb5ce 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -100,6 +100,24 @@
return false;
}
+void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size,
+ const void* data, GLenum usage) {
+ if (!*buffer) {
+ glGenBuffers(1, buffer);
+ }
+ bindMeshBuffer(*buffer);
+ glBufferData(GL_ARRAY_BUFFER, size, data, usage);
+}
+
+void MeshState::deleteMeshBuffer(GLuint buffer) {
+ if (buffer == mCurrentBuffer) {
+ // GL defines that deleting the currently bound VBO rebinds to 0 (no VBO).
+ // Reflect this in our cached value.
+ mCurrentBuffer = 0;
+ }
+ glDeleteBuffers(1, &buffer);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Vertices
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index e80f4d0..6c0fb78 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -75,6 +75,9 @@
*/
bool unbindMeshBuffer();
+ void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage);
+ void deleteMeshBuffer(GLuint);
+
///////////////////////////////////////////////////////////////////////////////
// Vertices
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c1f6670..b6fecb4 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -20,6 +20,7 @@
#include "Caches.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
+#include "LayerUpdateQueue.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
#include "Properties.h"
@@ -198,7 +199,11 @@
mCurrentFrameInfo->markSyncStart();
info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
+#endif
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -333,7 +338,8 @@
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+ OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+ mLayerUpdateQueue.clear();
BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
// TODO: profiler().draw(mCanvas);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -555,7 +561,11 @@
TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
+#endif
info.runAnimations = false;
node->prepareTree(info);
SkRect ignore;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 30e6562..d656014 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -18,9 +18,10 @@
#define CANVASCONTEXT_H_
#include "DamageAccumulator.h"
-#include "IContextFactory.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
+#include "IContextFactory.h"
+#include "LayerUpdateQueue.h"
#include "RenderNode.h"
#include "utils/RingBuffer.h"
#include "renderthread/RenderTask.h"
@@ -83,7 +84,7 @@
void draw();
void destroy();
- // IFrameCallback, Chroreographer-driven frame callback entry point
+ // IFrameCallback, Choreographer-driven frame callback entry point
virtual void doFrame() override;
void prepareAndDraw(RenderNode* node);
@@ -118,7 +119,7 @@
void addRenderNode(RenderNode* node, bool placeFront) {
int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
- mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+ mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
}
void removeRenderNode(RenderNode* node) {
@@ -166,6 +167,7 @@
OpenGLRenderer* mCanvas = nullptr;
bool mHaveNewSurface = false;
DamageAccumulator mDamageAccumulator;
+ LayerUpdateQueue mLayerUpdateQueue;
std::unique_ptr<AnimationContext> mAnimationContext;
std::vector< sp<RenderNode> > mRenderNodes;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 15ccd6a..a1107f0 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -563,10 +563,7 @@
void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
void* retval;
task->setReturnPtr(&retval);
- SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
- AutoMutex _lock(mSyncMutex);
- mRenderThread.queue(&syncTask);
- mSyncCondition.wait(mSyncMutex);
+ mRenderThread.queueAndWait(task);
return retval;
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 338fab6..d0e601e 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -117,9 +117,6 @@
DrawFrameTask mDrawFrameTask;
- Mutex mSyncMutex;
- Condition mSyncCondition;
-
void destroyContext();
void post(RenderTask* task);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 8fcd109..526a848 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -28,9 +28,6 @@
#include <utils/Log.h>
namespace android {
-using namespace uirenderer::renderthread;
-ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
-
namespace uirenderer {
namespace renderthread {
@@ -136,7 +133,22 @@
}
};
-RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+static bool gHasRenderThreadInstance = false;
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+RenderThread& RenderThread::getInstance() {
+ // This is a pointer because otherwise __cxa_finalize
+ // will try to delete it like a Good Citizen but that causes us to crash
+ // because we don't want to delete the RenderThread normally.
+ static RenderThread* sInstance = new RenderThread();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread() : Thread(true)
, mNextWakeup(LLONG_MAX)
, mDisplayEventReceiver(nullptr)
, mVsyncRequested(false)
@@ -313,13 +325,10 @@
}
void RenderThread::queueAndWait(RenderTask* task) {
- Mutex mutex;
- Condition condition;
- SignalingRenderTask syncTask(task, &mutex, &condition);
-
- AutoMutex _lock(mutex);
+ SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+ AutoMutex _lock(mSyncMutex);
queue(&syncTask);
- condition.wait(mutex);
+ mSyncCondition.wait(mSyncMutex);
}
void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f3444a8..d8c7e61 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -25,11 +25,11 @@
#include <cutils/compiler.h>
#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
-#include <utils/Mutex.h>
-#include <utils/Singleton.h>
#include <utils/Thread.h>
+#include <condition_variable>
#include <memory>
+#include <mutex>
#include <set>
namespace android {
@@ -72,7 +72,7 @@
~IFrameCallback() {}
};
-class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
+class ANDROID_API RenderThread : public Thread {
public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
@@ -100,7 +100,6 @@
virtual bool threadLoop() override;
private:
- friend class Singleton<RenderThread>;
friend class DispatchFrameCallbacks;
friend class RenderProxy;
friend class android::uirenderer::TestUtils;
@@ -108,6 +107,9 @@
RenderThread();
virtual ~RenderThread();
+ static bool hasInstance();
+ static RenderThread& getInstance();
+
void initThreadLocals();
void initializeDisplayEventReceiver();
static int displayEventReceiverCallback(int fd, int events, void* data);
@@ -125,6 +127,8 @@
nsecs_t mNextWakeup;
TaskQueue mQueue;
+ Mutex mSyncMutex;
+ Condition mSyncCondition;
DisplayInfo mDisplayInfo;
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 2eefd37..29d9803 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -24,6 +24,7 @@
#include <RenderNode.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
+#include <unit_tests/TestUtils.h>
#include "Benchmark.h"
#include "TestContext.h"
@@ -401,3 +402,27 @@
"Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
TreeContentAnimation::run<SaveLayerAnimation>
});
+
+
+class HwLayerAnimation : public TreeContentAnimation {
+public:
+ sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+ }, true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+ canvas->drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
+static Benchmark _HwLayer(BenchmarkInfo{
+ "hwlayer",
+ "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+ "Tests the hardware layer codepath.",
+ TreeContentAnimation::run<HwLayerAnimation>
+});
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index fb760ac5..3ef329a 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -56,6 +56,27 @@
}
}
+TEST(FatVector, preSizeConstructor) {
+ {
+ FatVector<int, 4> v(32);
+ EXPECT_EQ(32u, v.capacity());
+ EXPECT_EQ(32u, v.size());
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(4);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(4u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(2);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(2u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+}
+
TEST(FatVector, shrink) {
FatVector<int, 10> v;
EXPECT_TRUE(allocationIsInternal(v));
@@ -78,9 +99,9 @@
FatVector<TestUtils::SignalingDtor, 0> v;
v.emplace_back(&count);
EXPECT_FALSE(allocationIsInternal(v));
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
}
- EXPECT_EQ(1, count);
+ EXPECT_EQ(1, count) << "Destruction should happen exactly once";
}
TEST(FatVector, destructorExternal) {
@@ -92,7 +113,7 @@
v.emplace_back(&count);
EXPECT_TRUE(allocationIsInternal(v));
}
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
}
- EXPECT_EQ(10, count);
+ EXPECT_EQ(10, count) << "Destruction should happen exactly once";
}
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
new file mode 100644
index 0000000..9d625bc
--- /dev/null
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <LayerUpdateQueue.h>
+#include <RenderNode.h>
+
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(LayerUpdateQueue, construct) {
+ LayerUpdateQueue queue;
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+// sync node properties, so properties() reflects correct width and height
+static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
+ sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
+ TestUtils::syncNodePropertiesAndDisplayList(node);
+ return node;
+}
+
+TEST(LayerUpdateQueue, enqueueSimple) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+ sp<RenderNode> b = createSyncedNode(200, 200);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75));
+ queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300));
+
+ EXPECT_EQ(2u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
+ EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
+ EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
+}
+
+TEST(LayerUpdateQueue, enqueueUnion) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20));
+ queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40));
+
+ EXPECT_EQ(1u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
+}
+
+TEST(LayerUpdateQueue, clear) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(100, 100));
+
+ EXPECT_FALSE(queue.entries().empty());
+
+ queue.clear();
+
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+};
+};
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ffb575f..09b10c3 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -20,6 +20,7 @@
#include <OpReorderer.h>
#include <RecordedOp.h>
#include <RecordingCanvas.h>
+#include <renderthread/CanvasContext.h> // todo: remove
#include <unit_tests/TestUtils.h>
#include <unordered_map>
@@ -27,6 +28,7 @@
namespace android {
namespace uirenderer {
+LayerUpdateQueue sEmptyLayerUpdateQueue;
/**
* Virtual class implemented by each test to redirect static operation / state transitions to
@@ -42,14 +44,24 @@
class TestRendererBase {
public:
virtual ~TestRendererBase() {}
- virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
- virtual void endLayer() { ADD_FAILURE(); }
+ virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+ ADD_FAILURE() << "Layer creation not expected in this test";
+ return nullptr;
+ }
+ virtual void startLayer(OffscreenBuffer*) {
+ ADD_FAILURE() << "Layer repaint not expected in this test";
+ }
+ virtual void endLayer() {
+ ADD_FAILURE() << "Layer updates not expected in this test";
+ }
virtual void startFrame(uint32_t width, uint32_t height) {}
virtual void endFrame() {}
// define virtual defaults for direct
#define BASE_OP_METHOD(Type) \
- virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); }
+ virtual void on##Type(const Type&, const BakedOpState&) { \
+ ADD_FAILURE() << #Type " not expected in this test"; \
+ }
MAP_OPS(BASE_OP_METHOD)
int getIndex() { return mIndex; }
@@ -192,7 +204,8 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
- OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ SkRect::MakeWH(200, 200), 200, 200, nodes);
RenderNodeTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -216,7 +229,8 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
- OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+ OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
200, 200, nodes);
ClippedTestRenderer renderer;
@@ -226,7 +240,7 @@
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(180u, width);
EXPECT_EQ(180u, height);
@@ -268,13 +282,13 @@
/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - startLayer2, rect2 endLayer2
- * - startLayer1, rect1, drawLayer2, endLayer1
+ * - createLayer2, rect2 endLayer2
+ * - createLayer1, rect1, drawLayer2, endLayer1
* - startFrame, layerOp1, endFrame
*/
class SaveLayerNestedTestRenderer : public TestRendererBase {
public:
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
const int index = mIndex++;
if (index == 0) {
EXPECT_EQ(400u, width);
@@ -356,5 +370,162 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
+class HwLayerSimpleTestRenderer : public TestRendererBase {
+public:
+ void startLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+};
+TEST(OpReorderer, hwLayerSimple) {
+ sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ OffscreenBuffer** bufferHandle = node->getLayerHandle();
+ *bufferHandle = (OffscreenBuffer*) 0x0124;
+
+ TestUtils::syncNodePropertiesAndDisplayList(node);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(node.get());
+
+ // only enqueue partial damage
+ LayerUpdateQueue layerUpdateQueue;
+ layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+ HwLayerSimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(6, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *bufferHandle = nullptr;
+}
+
+
+/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+ * - startLayer(child), rect(grey), endLayer
+ * - createLayer, drawLayer(child), endLayer
+ * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+ * - startFrame, drawLayer(parent), endLayerb
+ */
+class HwLayerComplexTestRenderer : public TestRendererBase {
+public:
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(3, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void startLayer(OffscreenBuffer* offscreenBuffer) override {
+ int index = mIndex++;
+ if (index == 0) {
+ // starting inner layer
+ EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+ } else if (index == 6) {
+ // starting outer layer
+ EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+ } else { ADD_FAILURE(); }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 1) {
+ // inner layer's rect (white)
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ } else if (index == 7) {
+ // outer layer's rect (grey)
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ } else { ADD_FAILURE(); }
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(10, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 4) {
+ EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+ } else if (index == 11) {
+ EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+ } else { ADD_FAILURE(); }
+ }
+ void endFrame() override {
+ EXPECT_EQ(12, mIndex++);
+ }
+};
+TEST(OpReorderer, hwLayerComplex) {
+ sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ child->setPropertyFieldsDirty(RenderNode::GENERIC);
+ *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+
+ RenderNode* childPtr = child.get();
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(0, 0, 200, 200, paint);
+
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawRenderNode(childPtr);
+ canvas.restore();
+ });
+ parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ parent->setPropertyFieldsDirty(RenderNode::GENERIC);
+ *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+
+ TestUtils::syncNodePropertiesAndDisplayList(child);
+ TestUtils::syncNodePropertiesAndDisplayList(parent);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(parent.get());
+
+ LayerUpdateQueue layerUpdateQueue;
+ layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
+ layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
+
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+ HwLayerComplexTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(13, renderer.getIndex());
+
+ // clean up layer pointers, so we can safely destruct RenderNodes
+ *(child->getLayerHandle()) = nullptr;
+ *(parent->getLayerHandle()) = nullptr;
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index e8cdf46..dcf1f64 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -53,7 +53,7 @@
ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
});
- ASSERT_EQ(1, count); // only one observed
+ ASSERT_EQ(1, count);
}
TEST(RecordingCanvas, backgroundAndImage) {
@@ -106,7 +106,7 @@
}
count++;
});
- ASSERT_EQ(2, count); // two draws observed
+ ASSERT_EQ(2, count);
}
TEST(RecordingCanvas, saveLayerSimple) {
@@ -121,7 +121,9 @@
switch(count++) {
case 0:
EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
- // TODO: add asserts
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
break;
case 1:
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
@@ -132,7 +134,7 @@
break;
case 2:
EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
- // TODO: add asserts
+ // Don't bother asserting recording state data - it's not used
break;
default:
ADD_FAILURE();
@@ -155,10 +157,8 @@
if (count++ == 1) {
Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
- // recorded clip rect should be intersection of
- // viewport and saveLayer bounds, in layer space
- EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+ EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect) << "Recorded clip rect should be"
+ " intersection of viewport and saveLayer bounds, in layer space";
EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
expectedMatrix.loadTranslate(-100, -100, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -183,14 +183,11 @@
int count = 0;
playbackOps(*dl, [&count](const RecordedOp& op) {
if (count++ == 1) {
- Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
- // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
- expectedMatrix.loadIdentity();
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
+ << "Recorded op shouldn't see any canvas transform before the saveLayer";
}
});
EXPECT_EQ(3, count);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 5b09fda..770f413 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -89,15 +89,24 @@
return std::unique_ptr<DisplayList>(canvas.finishRecording());
}
- template<class CanvasType>
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(CanvasType& canvas)> canvasCallback) {
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ if (onLayer) {
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }
+ return node;
+ }
- CanvasType canvas(
- node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
+ template<class CanvasType>
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+ std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
+ sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
+
+ auto&& props = node->stagingProperties(); // staging, since not sync'd yet
+ CanvasType canvas(props.getWidth(), props.getHeight());
canvasCallback(canvas);
node->setStagingDisplayList(canvas.finishRecording());
return node;
@@ -108,7 +117,7 @@
node->syncDisplayList();
}
- typedef std::function<void(RenderState& state, Caches& caches)> RtCallback;
+ typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
class TestTask : public renderthread::RenderTask {
public:
@@ -120,7 +129,7 @@
RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
renderState.onGLContextCreated();
- rtCallback(renderState, Caches::getInstance());
+ rtCallback(renderthread::RenderThread::getInstance());
renderState.onGLContextDestroyed();
};
RtCallback rtCallback;
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
index c3c16c5a..315c249 100644
--- a/libs/hwui/utils/FatVector.h
+++ b/libs/hwui/utils/FatVector.h
@@ -91,6 +91,10 @@
InlineStdAllocator<T, SIZE>(mAllocation)) {
this->reserve(SIZE);
}
+
+ FatVector(size_t capacity) : FatVector() {
+ this->resize(capacity);
+ }
private:
typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
};
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 7f22b8a..45529ef 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -221,8 +221,9 @@
* @return An array of audio encodings (e.g. {@link AudioFormat#ENCODING_PCM_16BIT},
* {@link AudioFormat#ENCODING_PCM_FLOAT}) supported by the audio device.
* <code>ENCODING_PCM_FLOAT</code> indicates the device supports more
- * than 16 bits of integer precision. Specifying <code>ENCODING_PCM_FLOAT</code>
- * with {@link AudioTrack} or {@link AudioRecord} can preserve at least 24 bits of
+ * than 16 bits of integer precision. As there is no AudioFormat constant
+ * specifically defined for 24-bit PCM, the value <code>ENCODING_PCM_FLOAT</code>
+ * indicates that {@link AudioTrack} or {@link AudioRecord} can preserve at least 24 bits of
* integer precision to that device.
*
* @see AudioFormat
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 875e716..50df556 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3199,8 +3199,10 @@
/**
* Returns the value of the property with the specified key.
* @param key One of the strings corresponding to a property key: either
- * {@link #PROPERTY_OUTPUT_SAMPLE_RATE} or
- * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER}
+ * {@link #PROPERTY_OUTPUT_SAMPLE_RATE},
+ * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER},
+ * {@link #PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND}, or
+ * {@link #PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND}.
* @return A string representing the associated value for that property key,
* or null if there is no value for that key.
*/
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 4019d02..ada7f49 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -72,10 +72,10 @@
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/grid_padding_horiz"
- android:paddingEnd="@dimen/grid_padding_horiz"
- android:paddingTop="@dimen/grid_padding_vert"
- android:paddingBottom="@dimen/grid_padding_vert"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:drawSelectorOnTop="true"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index a09a22a..3b7da78 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -66,7 +66,6 @@
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.SimpleItemAnimator;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Formatter;
@@ -162,6 +161,9 @@
private MessageBar mMessageBar;
private View mProgressBar;
+ private int mSelectedItemColor;
+ private int mDefaultItemColor;
+
public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
show(fm, TYPE_NORMAL, root, doc, null, anim);
}
@@ -254,8 +256,7 @@
}
});
- // TODO: Restore transition animations. See b/24802917.
- ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false);
+ mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
// TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
if (DEBUG_ENABLE_DND) {
@@ -293,6 +294,13 @@
mAdapter = new DocumentsAdapter(context);
mRecView.setAdapter(mAdapter);
+ mDefaultItemColor = context.getResources().getColor(android.R.color.transparent);
+ // Get the accent color.
+ TypedValue selColor = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+ // Set the opacity to 10%.
+ mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000;
+
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@Override
@@ -669,7 +677,7 @@
getActivity().getWindow().setStatusBarColor(color.data);
if (mActionMode != null) {
- mActionMode.setTitle(TextUtils.formatSelectedCount(mSelected.size()));
+ mActionMode.setTitle(String.valueOf(mSelected.size()));
}
}
@@ -897,24 +905,26 @@
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
- private static final class DocumentHolder
+ private final class DocumentHolder
extends RecyclerView.ViewHolder
implements View.OnKeyListener
{
- // each data item is just a string in this case
- public View view;
public String docId; // The stable document id.
private ClickListener mClickListener;
private View.OnKeyListener mKeyListener;
public DocumentHolder(View view) {
super(view);
- this.view = view;
// Setting this using android:focusable in the item layouts doesn't work for list items.
// So we set it here. Note that touch mode focus is a separate issue - see
// View.setFocusableInTouchMode and View.isInTouchMode for more info.
- this.view.setFocusable(true);
- this.view.setOnKeyListener(this);
+ view.setFocusable(true);
+ view.setOnKeyListener(this);
+ }
+
+ public void setSelected(boolean selected) {
+ itemView.setActivated(selected);
+ itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
}
@Override
@@ -943,10 +953,10 @@
checkState(mKeyListener == null);
mKeyListener = listener;
}
+ }
- interface ClickListener {
- public void onClick(DocumentHolder doc);
- }
+ interface ClickListener {
+ public void onClick(DocumentHolder doc);
}
void showEmptyView() {
@@ -1005,6 +1015,24 @@
return holder;
}
+ /**
+ * Deal with selection changed events by using a custom ItemAnimator that just changes the
+ * background color. This works around focus issues (otherwise items lose focus when their
+ * selection state changes) but also optimizes change animations for selection.
+ */
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
+ final View itemView = holder.itemView;
+
+ if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) {
+ final boolean selected = isSelected(position);
+ itemView.setActivated(selected);
+ return;
+ } else {
+ onBindViewHolder(holder, position);
+ }
+ }
+
@Override
public void onBindViewHolder(DocumentHolder holder, int position) {
@@ -1030,8 +1058,9 @@
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
holder.docId = docId;
- final View itemView = holder.view;
- itemView.setActivated(isSelected(position));
+ final View itemView = holder.itemView;
+
+ holder.setSelected(isSelected(position));
final View line1 = itemView.findViewById(R.id.line1);
final View line2 = itemView.findViewById(R.id.line2);
@@ -1959,7 +1988,7 @@
}
}
- private class ItemClickListener implements DocumentHolder.ClickListener {
+ private class ItemClickListener implements ClickListener {
@Override
public void onClick(DocumentHolder doc) {
final int position = doc.getAdapterPosition();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
new file mode 100644
index 0000000..0eb1ea5
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 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.documentsui;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.support.v4.util.ArrayMap;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs change animations on Items in DirectoryFragment's RecyclerView. This class overrides
+ * the way selection animations are normally performed - instead of cross fading the old Item with a
+ * new Item, this class manually animates a background color change. This enables selected Items to
+ * correctly maintain focus.
+ */
+class DirectoryItemAnimator extends DefaultItemAnimator {
+ private final List<ColorAnimation> mPendingAnimations = new ArrayList<>();
+ private final Map<RecyclerView.ViewHolder, ColorAnimation> mRunningAnimations =
+ new ArrayMap<>();
+ private final Integer mDefaultColor;
+ private final Integer mSelectedColor;
+
+ public DirectoryItemAnimator(Context context) {
+ mDefaultColor = context.getResources().getColor(android.R.color.transparent);
+ // Get the accent color.
+ TypedValue selColor = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+ // Set the opacity to 10%.
+ mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ super.runPendingAnimations();
+ for (ColorAnimation anim: mPendingAnimations) {
+ anim.start();
+ mRunningAnimations.put(anim.viewHolder, anim);
+ }
+ mPendingAnimations.clear();
+ }
+
+ @Override
+ public void endAnimation(RecyclerView.ViewHolder vh) {
+ super.endAnimation(vh);
+
+ for (int i = mPendingAnimations.size() - 1; i >= 0; --i) {
+ ColorAnimation anim = mPendingAnimations.get(i);
+ if (anim.viewHolder == vh) {
+ mPendingAnimations.remove(i);
+ anim.end();
+ }
+ }
+
+ ColorAnimation anim = mRunningAnimations.get(vh);
+ if (anim != null) {
+ anim.cancel();
+ }
+ }
+
+ @Override
+ public ItemHolderInfo recordPreLayoutInformation(
+ RecyclerView.State state,
+ RecyclerView.ViewHolder viewHolder,
+ @AdapterChanges int changeFlags,
+ List<Object> payloads) {
+ ItemInfo info = (ItemInfo) super.recordPreLayoutInformation(state,
+ viewHolder, changeFlags, payloads);
+ info.isActivated = viewHolder.itemView.isActivated();
+ return info;
+ }
+
+
+ @Override
+ public ItemHolderInfo recordPostLayoutInformation(
+ RecyclerView.State state, RecyclerView.ViewHolder viewHolder) {
+ ItemInfo info = (ItemInfo) super.recordPostLayoutInformation(state,
+ viewHolder);
+ info.isActivated = viewHolder.itemView.isActivated();
+ return info;
+ }
+
+ @Override
+ public boolean animateChange(final RecyclerView.ViewHolder oldHolder,
+ RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
+ ItemHolderInfo postInfo) {
+ if (oldHolder != newHolder) {
+ return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
+ }
+
+ ItemInfo pre = (ItemInfo)preInfo;
+ ItemInfo post = (ItemInfo)postInfo;
+
+ if (pre.isActivated == post.isActivated) {
+ dispatchAnimationFinished(oldHolder);
+ return false;
+ } else {
+ Integer startColor = pre.isActivated ? mSelectedColor : mDefaultColor;
+ Integer endColor = post.isActivated ? mSelectedColor : mDefaultColor;
+ oldHolder.itemView.setBackgroundColor(startColor);
+ mPendingAnimations.add(new ColorAnimation(oldHolder, startColor, endColor));
+ }
+ return true;
+ }
+
+ @Override
+ public ItemHolderInfo obtainHolderInfo() {
+ return new ItemInfo();
+ }
+
+ @Override
+ public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder vh) {
+ return true;
+ }
+
+ class ItemInfo extends DefaultItemAnimator.ItemHolderInfo {
+ boolean isActivated;
+ };
+
+ /**
+ * Animates changes in background color.
+ */
+ class ColorAnimation
+ implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+ ValueAnimator mValueAnimator;
+ final RecyclerView.ViewHolder viewHolder;
+ int mEndColor;
+
+ public ColorAnimation(RecyclerView.ViewHolder vh, int startColor, int endColor)
+ {
+ viewHolder = vh;
+ mValueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
+ mValueAnimator.addUpdateListener(this);
+ mValueAnimator.addListener(this);
+
+ mEndColor = endColor;
+ }
+
+ public void start() {
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public void end() {
+ mValueAnimator.end();
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ viewHolder.itemView.setBackgroundColor((Integer)animator.getAnimatedValue());
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ viewHolder.itemView.setBackgroundColor(mEndColor);
+ mRunningAnimations.remove(viewHolder);
+ dispatchAnimationFinished(viewHolder);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ dispatchAnimationStarted(viewHolder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+};
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index 14a33f9..26a3734 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -34,6 +34,7 @@
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.Spinner;
@@ -49,7 +50,6 @@
import java.util.List;
public class ManageRootActivity extends BaseActivity {
- private static final int CODE_FORWARD = 42;
private static final String TAG = "ManageRootsActivity";
private Toolbar mToolbar;
@@ -140,6 +140,21 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
+
+ final MenuItem advanced = menu.findItem(R.id.menu_advanced);
+ final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+ final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
+ final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+ final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
+ final MenuItem search = menu.findItem(R.id.menu_search);
+
+ advanced.setVisible(false);
+ createDir.setVisible(false);
+ pasteFromCb.setEnabled(false);
+ newWindow.setEnabled(false);
+ fileSize.setVisible(false);
+ search.setVisible(false);
+
Menus.disableHiddenItems(menu);
return true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index ef53d53..4fde6ff 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -76,6 +76,9 @@
private Adapter<?> mAdapter;
private boolean mSingleSelect;
+ // Payloads for notifyItemChange to distinguish between selection and other events.
+ public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
@Nullable private BandController mBandManager;
/**
@@ -460,7 +463,7 @@
for (int i = lastListener; i > -1; i--) {
mCallbacks.get(i).onItemStateChanged(position, selected);
}
- mAdapter.notifyItemChanged(position);
+ mAdapter.notifyItemChanged(position, SELECTION_CHANGED_MARKER);
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index d75b6fd..beff196 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
+
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -30,6 +32,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.Formatter;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -56,12 +59,13 @@
*/
public class RootsFragment extends Fragment {
+ private static final String TAG = "RootsFragment";
+ private static final String EXTRA_INCLUDE_APPS = "includeApps";
+
private ListView mList;
private RootsAdapter mAdapter;
-
private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
- private static final String EXTRA_INCLUDE_APPS = "includeApps";
public static void show(FragmentManager fm, Intent includeApps) {
final Bundle args = new Bundle();
@@ -180,6 +184,8 @@
} else if (item instanceof AppItem) {
DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
activity.onAppPicked(((AppItem) item).info);
+ } else if (item instanceof SpacerItem) {
+ if (DEBUG) Log.d(TAG, "Ignoring click on spacer item.");
} else {
throw new IllegalStateException("Unknown root: " + item);
}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index 2a540d4..b65ac98 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -8,7 +8,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
LOCAL_PACKAGE_NAME := DocumentsUITests
LOCAL_INSTRUMENTATION_FOR := DocumentsUI
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index fc42c3b..6f1a89b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -28,6 +28,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
@@ -36,6 +37,7 @@
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
+
import com.google.common.collect.Lists;
import libcore.io.IoUtils;
@@ -52,6 +54,404 @@
public class CopyTest extends ServiceTestCase<CopyService> {
+ public CopyTest() {
+ super(CopyService.class);
+ }
+
+ private static String AUTHORITY = "com.android.documentsui.stubprovider";
+ private static String SRC_ROOT = StubProvider.ROOT_0_ID;
+ private static String DST_ROOT = StubProvider.ROOT_1_ID;
+ private static String TAG = "CopyTest";
+
+ private Context mContext;
+ private TestContentResolver mResolver;
+ private ContentProviderClient mClient;
+ private DocumentsProviderHelper mDocHelper;
+ private StubProvider mStorage;
+ private Context mSystemContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setupTestContext();
+ mClient = mResolver.acquireContentProviderClient(AUTHORITY);
+
+ // Reset the stub provider's storage.
+ mStorage.clearCacheAndBuildRoots();
+
+ mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mClient.release();
+ super.tearDown();
+ }
+
+ /**
+ * Test copying a single file.
+ */
+ public void testCopyFile() throws Exception {
+ String srcPath = "/test0.txt";
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+ "The five boxing wizards jump quickly".getBytes());
+
+ assertDstFileCountEquals(0);
+
+ startService(createCopyIntent(Lists.newArrayList(testFile)));
+
+ // 2 operations: file creation, then writing data.
+ mResolver.waitForChanges(2);
+
+ // Verify that one file was copied; check file contents.
+ assertDstFileCountEquals(1);
+ assertCopied(srcPath);
+ }
+
+ public void testMoveFile() throws Exception {
+ String srcPath = "/test0.txt";
+ String testContent = "The five boxing wizards jump quickly";
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
+
+ assertDstFileCountEquals(0);
+
+ Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
+ moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+ startService(moveIntent);
+
+ // 3 operations: file creation, writing data, deleting original.
+ mResolver.waitForChanges(3);
+
+ // Verify that one file was moved; check file contents.
+ assertDstFileCountEquals(1);
+ assertDoesNotExist(SRC_ROOT, srcPath);
+
+ byte[] dstContent = readFile(DST_ROOT, srcPath);
+ MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
+ }
+
+ /**
+ * Test copying multiple files.
+ */
+ public void testCopyMultipleFiles() throws Exception {
+ String testContent[] = {
+ "The five boxing wizards jump quickly",
+ "The quick brown fox jumps over the lazy dog",
+ "Jackdaws love my big sphinx of quartz"
+ };
+ String srcPaths[] = {
+ "/test0.txt",
+ "/test1.txt",
+ "/test2.txt"
+ };
+ List<Uri> testFiles = Lists.newArrayList(
+ mStorage.createFile(SRC_ROOT, srcPaths[0], "text/plain", testContent[0].getBytes()),
+ mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
+ mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
+
+ assertDstFileCountEquals(0);
+
+ // Copy all the test files.
+ startService(createCopyIntent(testFiles));
+
+ // 3 file creations, 3 file writes.
+ mResolver.waitForChanges(6);
+
+ assertDstFileCountEquals(3);
+ for (String path : srcPaths) {
+ assertCopied(path);
+ }
+ }
+
+ public void testCopyEmptyDir() throws Exception {
+ String srcPath = "/emptyDir";
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+ null);
+
+ assertDstFileCountEquals(0);
+
+ startService(createCopyIntent(Lists.newArrayList(testDir)));
+
+ // Just 1 operation: Directory creation.
+ mResolver.waitForChanges(1);
+
+ assertDstFileCountEquals(1);
+
+ // Verify that the dst exists and is a directory.
+ File dst = mStorage.getFile(DST_ROOT, srcPath);
+ assertTrue(dst.isDirectory());
+ }
+
+ public void testMoveEmptyDir() throws Exception {
+ String srcPath = "/emptyDir";
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+ null);
+
+ assertDstFileCountEquals(0);
+
+ Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+ moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+ startService(moveIntent);
+
+ // 2 operations: Directory creation, and removal of the original.
+ mResolver.waitForChanges(2);
+
+ assertDstFileCountEquals(1);
+
+ // Verify that the dst exists and is a directory.
+ File dst = mStorage.getFile(DST_ROOT, srcPath);
+ assertTrue(dst.isDirectory());
+
+ // Verify that the src was cleaned up.
+ assertDoesNotExist(SRC_ROOT, srcPath);
+ }
+
+ public void testMovePopulatedDir() throws Exception {
+ String testContent[] = {
+ "The five boxing wizards jump quickly",
+ "The quick brown fox jumps over the lazy dog",
+ "Jackdaws love my big sphinx of quartz"
+ };
+ String srcDir = "/testdir";
+ String srcFiles[] = {
+ srcDir + "/test0.txt",
+ srcDir + "/test1.txt",
+ srcDir + "/test2.txt"
+ };
+ // Create test dir; put some files in it.
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+ null);
+ mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+
+ Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+ moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+ startService(moveIntent);
+
+ // dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
+ // files.
+ mResolver.waitForChanges(11);
+
+ // Check the content of the moved files.
+ File dst = mStorage.getFile(DST_ROOT, srcDir);
+ assertTrue(dst.isDirectory());
+ for (int i = 0; i < testContent.length; ++i) {
+ byte[] dstContent = readFile(DST_ROOT, srcFiles[i]);
+ MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
+ dstContent);
+ }
+
+ // Check that the src files were removed.
+ assertDoesNotExist(SRC_ROOT, srcDir);
+ for (String srcFile : srcFiles) {
+ assertDoesNotExist(SRC_ROOT, srcFile);
+ }
+ }
+
+ public void testCopyFileWithReadErrors() throws Exception {
+ String srcPath = "/test0.txt";
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+ "The five boxing wizards jump quickly".getBytes());
+
+ assertDstFileCountEquals(0);
+
+ mStorage.simulateReadErrorsForFile(testFile);
+
+ startService(createCopyIntent(Lists.newArrayList(testFile)));
+
+ // 3 operations: file creation, writing, then deletion (due to failed copy).
+ mResolver.waitForChanges(3);
+
+ // Verify that the failed copy was cleaned up.
+ assertDstFileCountEquals(0);
+ }
+
+ public void testMoveFileWithReadErrors() throws Exception {
+ String srcPath = "/test0.txt";
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+ "The five boxing wizards jump quickly".getBytes());
+
+ assertDstFileCountEquals(0);
+
+ mStorage.simulateReadErrorsForFile(testFile);
+
+ Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
+ moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+ startService(moveIntent);
+
+ try {
+ // There should be 3 operations: file creation, writing, then deletion (due to failed
+ // copy). Wait for 4, in case the CopyService also attempts to do extra stuff (like
+ // delete the src file). This should time out.
+ mResolver.waitForChanges(4);
+ } catch (TimeoutException e) {
+ // Success path
+ return;
+ } finally {
+ // Verify that the failed copy was cleaned up, and the src file wasn't removed.
+ assertDstFileCountEquals(0);
+ assertExists(SRC_ROOT, srcPath);
+ }
+ // The asserts above didn't fail, but the CopyService did something unexpected.
+ fail("Extra file operations were detected");
+ }
+
+ public void testMoveDirectoryWithReadErrors() throws Exception {
+ String testContent[] = {
+ "The five boxing wizards jump quickly",
+ "The quick brown fox jumps over the lazy dog",
+ "Jackdaws love my big sphinx of quartz"
+ };
+ String srcDir = "/testdir";
+ String srcFiles[] = {
+ srcDir + "/test0.txt",
+ srcDir + "/test1.txt",
+ srcDir + "/test2.txt"
+ };
+ // Create test dir; put some files in it.
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+ null);
+ mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+ Uri errFile = mStorage
+ .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+
+ mStorage.simulateReadErrorsForFile(errFile);
+
+ Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
+ moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
+ startService(moveIntent);
+
+ // - dst dir creation,
+ // - creation and writing of 2 files, removal of 2 src files
+ // - creation and writing of 1 file, then removal of that file (due to error)
+ mResolver.waitForChanges(10);
+
+ // Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
+ // because it should contain the one errFile.
+ assertTrue(mStorage.getFile(SRC_ROOT, srcDir).isDirectory());
+ assertTrue(mStorage.getFile(DST_ROOT, srcDir).isDirectory());
+
+ // Check the content of the moved files.
+ MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
+ readFile(DST_ROOT, srcFiles[0]));
+ MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
+ readFile(DST_ROOT, srcFiles[2]));
+
+ // Check that the src files were removed.
+ assertDoesNotExist(SRC_ROOT, srcFiles[0]);
+ assertDoesNotExist(SRC_ROOT, srcFiles[2]);
+
+ // Check that the error file was not copied over.
+ assertDoesNotExist(DST_ROOT, srcFiles[1]);
+ assertExists(SRC_ROOT, srcFiles[1]);
+ }
+
+ /**
+ * Copies the given files to a pre-determined destination.
+ *
+ * @throws FileNotFoundException
+ */
+ private Intent createCopyIntent(List<Uri> srcs) throws Exception {
+ final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
+ for (Uri src : srcs) {
+ srcDocs.add(DocumentInfo.fromUri(mResolver, src));
+ }
+
+ RootInfo root = mDocHelper.getRoot(DST_ROOT);
+ final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
+ DocumentStack stack = new DocumentStack();
+ stack.push(DocumentInfo.fromUri(mResolver, dst));
+ final Intent copyIntent = new Intent(mContext, CopyService.class);
+ copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
+ copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+
+ // startService(copyIntent);
+ return copyIntent;
+ }
+
+ /**
+ * Returns a count of the files in the given directory.
+ */
+ private void assertDstFileCountEquals(int expected) throws RemoteException {
+ RootInfo dest = mDocHelper.getRoot(DST_ROOT);
+ final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
+ dest.documentId);
+ Cursor c = null;
+ int count = 0;
+ try {
+ c = mClient.query(queryUri, null, null, null, null);
+ count = c.getCount();
+ } finally {
+ IoUtils.closeQuietly(c);
+ }
+ assertEquals("Incorrect file count after copy", expected, count);
+ }
+
+ private void assertExists(String rootId, String path) throws Exception {
+ assertNotNull("An expected file was not found: " + path + " on root " + rootId,
+ mStorage.getFile(rootId, path));
+ }
+
+ private void assertDoesNotExist(String rootId, String path) throws Exception {
+ assertNull("Unexpected file found: " + path + " on root " + rootId,
+ mStorage.getFile(rootId, path));
+ }
+
+ private byte[] readFile(String rootId, String path) throws Exception {
+ File file = mStorage.getFile(rootId, path);
+ byte[] buf = null;
+ assertNotNull(file);
+
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ buf = Streams.readFully(in);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ return buf;
+ }
+
+ private void assertCopied(String path) throws Exception {
+ MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC_ROOT, path),
+ readFile(DST_ROOT, path));
+ }
+
+ /**
+ * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
+ * listen for notification events, to gauge copy progress.
+ *
+ * @throws FileNotFoundException
+ */
+ private void setupTestContext() throws FileNotFoundException {
+ mSystemContext = getSystemContext();
+
+ // Set up the context with the test content resolver.
+ mResolver = new TestContentResolver();
+ mContext = new ContextWrapper(mSystemContext) {
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+ };
+ setContext(mContext);
+
+ // Create a local stub provider and add it to the content resolver.
+ ProviderInfo info = new ProviderInfo();
+ info.authority = AUTHORITY;
+ info.exported = true;
+ info.grantUriPermissions = true;
+ info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+ info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
+
+ mStorage = new StubProvider();
+ mStorage.attachInfo(mContext, info);
+ mResolver.addProvider(AUTHORITY, mStorage);
+ }
+
/**
* A test resolver that enables this test suite to listen for notifications that mark when copy
* operations are done.
@@ -103,410 +503,4 @@
}
}
};
-
- public CopyTest() {
- super(CopyService.class);
- }
-
- private static String AUTHORITY = "com.android.documentsui.stubprovider";
- private static String DST = "sd1";
- private static String SRC = "sd0";
- private static String TAG = "CopyTest";
- private List<RootInfo> mRoots;
- private Context mContext;
- private TestContentResolver mResolver;
- private ContentProviderClient mClient;
- private StubProvider mStorage;
- private Context mSystemContext;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- setupTestContext();
- mClient = mResolver.acquireContentProviderClient(AUTHORITY);
-
- // Reset the stub provider's storage.
- mStorage.clearCacheAndBuildRoots();
-
- mRoots = Lists.newArrayList();
- Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY);
- Cursor cursor = null;
- try {
- cursor = mClient.query(queryUri, null, null, null, null);
- while (cursor.moveToNext()) {
- mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor));
- }
- } finally {
- IoUtils.closeQuietly(cursor);
- }
-
- }
-
- @Override
- protected void tearDown() throws Exception {
- mClient.release();
- super.tearDown();
- }
-
- /**
- * Test copying a single file.
- */
- public void testCopyFile() throws Exception {
- String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
- "The five boxing wizards jump quickly".getBytes());
-
- assertDstFileCountEquals(0);
-
- startService(createCopyIntent(Lists.newArrayList(testFile)));
-
- // 2 operations: file creation, then writing data.
- mResolver.waitForChanges(2);
-
- // Verify that one file was copied; check file contents.
- assertDstFileCountEquals(1);
- assertCopied(srcPath);
- }
-
- public void testMoveFile() throws Exception {
- String srcPath = "/test0.txt";
- String testContent = "The five boxing wizards jump quickly";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", testContent.getBytes());
-
- assertDstFileCountEquals(0);
-
- Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
- moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
- startService(moveIntent);
-
- // 3 operations: file creation, writing data, deleting original.
- mResolver.waitForChanges(3);
-
- // Verify that one file was moved; check file contents.
- assertDstFileCountEquals(1);
- assertDoesNotExist(SRC, srcPath);
-
- byte[] dstContent = readFile(DST, srcPath);
- MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
- }
-
- /**
- * Test copying multiple files.
- */
- public void testCopyMultipleFiles() throws Exception {
- String testContent[] = {
- "The five boxing wizards jump quickly",
- "The quick brown fox jumps over the lazy dog",
- "Jackdaws love my big sphinx of quartz"
- };
- String srcPaths[] = {
- "/test0.txt",
- "/test1.txt",
- "/test2.txt"
- };
- List<Uri> testFiles = Lists.newArrayList(
- mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()),
- mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()),
- mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes()));
-
- assertDstFileCountEquals(0);
-
- // Copy all the test files.
- startService(createCopyIntent(testFiles));
-
- // 3 file creations, 3 file writes.
- mResolver.waitForChanges(6);
-
- assertDstFileCountEquals(3);
- for (String path : srcPaths) {
- assertCopied(path);
- }
- }
-
- public void testCopyEmptyDir() throws Exception {
- String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
-
- startService(createCopyIntent(Lists.newArrayList(testDir)));
-
- // Just 1 operation: Directory creation.
- mResolver.waitForChanges(1);
-
- assertDstFileCountEquals(1);
-
- // Verify that the dst exists and is a directory.
- File dst = mStorage.getFile(DST, srcPath);
- assertTrue(dst.isDirectory());
- }
-
- public void testMoveEmptyDir() throws Exception {
- String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
-
- Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
- moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
- startService(moveIntent);
-
- // 2 operations: Directory creation, and removal of the original.
- mResolver.waitForChanges(2);
-
- assertDstFileCountEquals(1);
-
- // Verify that the dst exists and is a directory.
- File dst = mStorage.getFile(DST, srcPath);
- assertTrue(dst.isDirectory());
-
- // Verify that the src was cleaned up.
- assertDoesNotExist(SRC, srcPath);
- }
-
- public void testMovePopulatedDir() throws Exception {
- String testContent[] = {
- "The five boxing wizards jump quickly",
- "The quick brown fox jumps over the lazy dog",
- "Jackdaws love my big sphinx of quartz"
- };
- String srcDir = "/testdir";
- String srcFiles[] = {
- srcDir + "/test0.txt",
- srcDir + "/test1.txt",
- srcDir + "/test2.txt"
- };
- // Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
- mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
- mStorage.createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
- mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
-
- Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
- moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
- startService(moveIntent);
-
- // dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
- // files.
- mResolver.waitForChanges(11);
-
- // Check the content of the moved files.
- File dst = mStorage.getFile(DST, srcDir);
- assertTrue(dst.isDirectory());
- for (int i = 0; i < testContent.length; ++i) {
- byte[] dstContent = readFile(DST, srcFiles[i]);
- MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
- dstContent);
- }
-
- // Check that the src files were removed.
- assertDoesNotExist(SRC, srcDir);
- for (String srcFile : srcFiles) {
- assertDoesNotExist(SRC, srcFile);
- }
- }
-
- public void testCopyFileWithReadErrors() throws Exception {
- String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
- "The five boxing wizards jump quickly".getBytes());
-
- assertDstFileCountEquals(0);
-
- mStorage.simulateReadErrorsForFile(testFile);
-
- startService(createCopyIntent(Lists.newArrayList(testFile)));
-
- // 3 operations: file creation, writing, then deletion (due to failed copy).
- mResolver.waitForChanges(3);
-
- // Verify that the failed copy was cleaned up.
- assertDstFileCountEquals(0);
- }
-
- public void testMoveFileWithReadErrors() throws Exception {
- String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
- "The five boxing wizards jump quickly".getBytes());
-
- assertDstFileCountEquals(0);
-
- mStorage.simulateReadErrorsForFile(testFile);
-
- Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
- moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
- startService(moveIntent);
-
- try {
- // There should be 3 operations: file creation, writing, then deletion (due to failed
- // copy). Wait for 4, in case the CopyService also attempts to do extra stuff (like
- // delete the src file). This should time out.
- mResolver.waitForChanges(4);
- } catch (TimeoutException e) {
- // Success path
- return;
- } finally {
- // Verify that the failed copy was cleaned up, and the src file wasn't removed.
- assertDstFileCountEquals(0);
- assertExists(SRC, srcPath);
- }
- // The asserts above didn't fail, but the CopyService did something unexpected.
- fail("Extra file operations were detected");
- }
-
- public void testMoveDirectoryWithReadErrors() throws Exception {
- String testContent[] = {
- "The five boxing wizards jump quickly",
- "The quick brown fox jumps over the lazy dog",
- "Jackdaws love my big sphinx of quartz"
- };
- String srcDir = "/testdir";
- String srcFiles[] = {
- srcDir + "/test0.txt",
- srcDir + "/test1.txt",
- srcDir + "/test2.txt"
- };
- // Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
- mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
- Uri errFile = mStorage
- .createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
- mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
-
- mStorage.simulateReadErrorsForFile(errFile);
-
- Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
- moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
- startService(moveIntent);
-
- // - dst dir creation,
- // - creation and writing of 2 files, removal of 2 src files
- // - creation and writing of 1 file, then removal of that file (due to error)
- mResolver.waitForChanges(10);
-
- // Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
- // because it should contain the one errFile.
- assertTrue(mStorage.getFile(SRC, srcDir).isDirectory());
- assertTrue(mStorage.getFile(DST, srcDir).isDirectory());
-
- // Check the content of the moved files.
- MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
- readFile(DST, srcFiles[0]));
- MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
- readFile(DST, srcFiles[2]));
-
- // Check that the src files were removed.
- assertDoesNotExist(SRC, srcFiles[0]);
- assertDoesNotExist(SRC, srcFiles[2]);
-
- // Check that the error file was not copied over.
- assertDoesNotExist(DST, srcFiles[1]);
- assertExists(SRC, srcFiles[1]);
- }
-
- /**
- * Copies the given files to a pre-determined destination.
- *
- * @throws FileNotFoundException
- */
- private Intent createCopyIntent(List<Uri> srcs) throws FileNotFoundException {
- final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
- for (Uri src : srcs) {
- srcDocs.add(DocumentInfo.fromUri(mResolver, src));
- }
-
- final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId);
- DocumentStack stack = new DocumentStack();
- stack.push(DocumentInfo.fromUri(mResolver, dst));
- final Intent copyIntent = new Intent(mContext, CopyService.class);
- copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
- copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
-
- // startService(copyIntent);
- return copyIntent;
- }
-
- /**
- * Returns a count of the files in the given directory.
- */
- private void assertDstFileCountEquals(int expected) throws RemoteException {
- final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
- mRoots.get(1).documentId);
- Cursor c = null;
- int count = 0;
- try {
- c = mClient.query(queryUri, null, null, null, null);
- count = c.getCount();
- } finally {
- IoUtils.closeQuietly(c);
- }
- assertEquals("Incorrect file count after copy", expected, count);
- }
-
- private void assertExists(String rootId, String path) throws Exception {
- assertNotNull("An expected file was not found: " + path + " on root " + rootId,
- mStorage.getFile(rootId, path));
- }
-
- private void assertDoesNotExist(String rootId, String path) throws Exception {
- assertNull("Unexpected file found: " + path + " on root " + rootId,
- mStorage.getFile(rootId, path));
- }
-
- private byte[] readFile(String rootId, String path) throws Exception {
- File file = mStorage.getFile(rootId, path);
- byte[] buf = null;
- assertNotNull(file);
-
- FileInputStream in = null;
- try {
- in = new FileInputStream(file);
- buf = Streams.readFully(in);
- } finally {
- IoUtils.closeQuietly(in);
- }
- return buf;
- }
-
- private void assertCopied(String path) throws Exception {
- MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC, path),
- readFile(DST, path));
- }
-
- /**
- * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
- * listen for notification events, to gauge copy progress.
- *
- * @throws FileNotFoundException
- */
- private void setupTestContext() throws FileNotFoundException {
- mSystemContext = getSystemContext();
-
- // Set up the context with the test content resolver.
- mResolver = new TestContentResolver();
- mContext = new ContextWrapper(mSystemContext) {
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
- };
- setContext(mContext);
-
- // Create a local stub provider and add it to the content resolver.
- ProviderInfo info = new ProviderInfo();
- info.authority = AUTHORITY;
- info.exported = true;
- info.grantUriPermissions = true;
- info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
- info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
-
- mStorage = new StubProvider();
- mStorage.attachInfo(mContext, info);
- mResolver.addProvider(AUTHORITY, mStorage);
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
new file mode 100644
index 0000000..7abc99c
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.documentsui;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.ContentProviderClient;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import com.android.documentsui.model.RootInfo;
+
+import libcore.io.IoUtils;
+
+/**
+ * Provides support for creation of documents in a test settings.
+ */
+public class DocumentsProviderHelper {
+
+ private final ContentProviderClient mClient;
+ private final String mAuthority;
+
+ public DocumentsProviderHelper(String authority, ContentProviderClient client) {
+ mClient = client;
+ mAuthority = authority;
+ }
+
+ public RootInfo getRoot(String id) throws RemoteException {
+ final Uri rootsUri = DocumentsContract.buildRootsUri(mAuthority);
+
+ Cursor cursor = null;
+ try {
+ cursor = mClient.query(rootsUri, null, null, null, null);
+ while (cursor.moveToNext()) {
+ if (id.equals(getCursorString(cursor, Root.COLUMN_ROOT_ID))) {
+ return RootInfo.fromRootsCursor(mAuthority, cursor);
+ }
+ }
+ throw new IllegalArgumentException("Can't find matching root for id=" + id);
+ } catch (Exception e) {
+ throw new RuntimeException("Can't load root for id=" + id , e);
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+ }
+
+ public Uri createDocument(Uri parentUri, String mimeType, String name) {
+ if (name.contains("/")) {
+ throw new IllegalArgumentException("Name and mimetype probably interposed.");
+ }
+ try {
+ return DocumentsContract.createDocument(mClient, parentUri, mimeType, name);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Couldn't create document: " + name + " with mimetype " + mimeType, e);
+ }
+ }
+
+ public Uri createFolder(Uri parentUri, String name) {
+ return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
+ }
+
+ public Uri createDocument(RootInfo root, String mimeType, String name) {
+ Uri rootUri = DocumentsContract.buildDocumentUri(mAuthority, root.documentId);
+ return createDocument(rootUri, mimeType, name);
+ }
+
+ public Uri createFolder(RootInfo root, String name) {
+ return createDocument(root, Document.MIME_TYPE_DIR, name);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 1f4b751..9060516 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -16,83 +16,158 @@
package com.android.documentsui;
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.RemoteException;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
+import android.util.Log;
import android.view.MotionEvent;
-import java.util.concurrent.TimeoutException;
+import com.android.documentsui.model.RootInfo;
public class FilesActivityUiTest extends InstrumentationTestCase {
+ private static final int TIMEOUT = 5000;
private static final String TAG = "FilesActivityUiTest";
private static final String TARGET_PKG = "com.android.documentsui";
private static final String LAUNCHER_PKG = "com.android.launcher";
- private static final int ONE_SECOND = 1000;
- private static final int FIVE_SECONDS = 5 * ONE_SECOND;
- private ActionBar mBar;
+ private UiBot mBot;
private UiDevice mDevice;
private Context mContext;
+ private ContentResolver mResolver;
+ private DocumentsProviderHelper mDocsHelper;
+ private ContentProviderClient mClient;
+ private RootInfo mRoot_0;
+ private RootInfo mRoot_1;
- public void setUp() throws TimeoutException {
+ public void setUp() throws Exception {
// Initialize UiDevice instance.
- mDevice = UiDevice.getInstance(getInstrumentation());
+ Instrumentation instrumentation = getInstrumentation();
+
+ mDevice = UiDevice.getInstance(instrumentation);
Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
// Start from the home screen.
mDevice.pressHome();
- mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS);
+ mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = instrumentation.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
// Launch app.
- mContext = getInstrumentation().getContext();
Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
// Wait for the app to appear.
- mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS);
+ mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
mDevice.waitForIdle();
- mBar = new ActionBar();
+ mBot = new UiBot(mDevice, TIMEOUT);
+
+ resetStorage(); // Just incase a test failed and tearDown didn't happen.
}
- public void testSwitchMode() throws Exception {
- UiObject2 mode = mBar.gridMode(100);
- if (mode != null) {
- mode.click();
- assertNotNull(mBar.listMode(ONE_SECOND));
- } else {
- mBar.listMode(100).click();
- assertNotNull(mBar.gridMode(ONE_SECOND));
- }
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ Log.d(TAG, "Resetting storage from setUp");
+ resetStorage();
+ mClient.release();
}
- private class ActionBar {
+ private void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ // TODO: Would be nice to have an event to wait on here.
+ mDevice.waitForIdle();
+ }
- public UiObject2 gridMode(int timeout) {
- // Note that we're using By.desc rather than By.res, because of b/25285770
- BySelector selector = By.desc("Grid view");
- if (timeout > 0) {
- mDevice.wait(Until.findObject(selector), timeout);
- }
- return mDevice.findObject(selector);
- }
+ private void initTestFiles() throws RemoteException {
+ mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
+ mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
- public UiObject2 listMode(int timeout) {
- // Note that we're using By.desc rather than By.res, because of b/25285770
- BySelector selector = By.desc("List view");
- if (timeout > 0) {
- mDevice.wait(Until.findObject(selector), timeout);
- }
- return mDevice.findObject(selector);
- }
+ mDocsHelper.createDocument(mRoot_0, "text/plain", "file0.log");
+ mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
+ mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+ }
+
+ public void testRootsListed() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ // Should also have Drive, but that requires pre-configuration of devices
+ // We omit for now.
+ mBot.assertHasRoots(
+ "Images",
+ "Videos",
+ "Audio",
+ "Downloads",
+ ROOT_0_ID,
+ ROOT_1_ID);
+ }
+
+ public void testFilesListed() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+ }
+
+ public void testFilesList_LiveUpdate() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+ mDocsHelper.createDocument(mRoot_0, "yummers/sandwich", "Ham & Cheese.sandwich");
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+ }
+
+ public void testDeleteDocument() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ mBot.clickDocument("file1.png");
+ mDevice.waitForIdle();
+ mBot.menuDelete().click();
+
+ mBot.waitForDeleteSnackbar();
+ assertFalse(mBot.hasDocuments("file1.png"));
+
+ mBot.waitForDeleteSnackbarGone();
+ assertFalse(mBot.hasDocuments("file1.png"));
+
+ // Now delete from another root.
+ mBot.openRoot(ROOT_1_ID);
+
+ mBot.clickDocument("poodles.text");
+ mDevice.waitForIdle();
+ mBot.menuDelete().click();
+
+ mBot.waitForDeleteSnackbar();
+ assertFalse(mBot.hasDocuments("poodles.text"));
+
+ mBot.waitForDeleteSnackbarGone();
+ assertFalse(mBot.hasDocuments("poodles.text"));
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 6a2e03a..2d42ddc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -50,12 +50,17 @@
import java.util.Map;
public class StubProvider extends DocumentsProvider {
+
+ public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stubprovider";
+ public static final String ROOT_0_ID = "TEST_ROOT_0";
+ public static final String ROOT_1_ID = "TEST_ROOT_1";
+
+ private static final String TAG = "StubProvider";
private static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
private static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
- private static int DEFAULT_SIZE = 1024 * 1024; // 1 MB.
- private static final String TAG = "StubProvider";
- private static final String MY_ROOT_ID = "sd0";
+ private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
+
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES
@@ -65,11 +70,12 @@
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
- private HashMap<String, StubDocument> mStorage = new HashMap<String, StubDocument>();
- private Object mWriteLock = new Object();
- private String mAuthority;
+ private final Map<String, StubDocument> mStorage = new HashMap<>();
+ private final Map<String, RootInfo> mRoots = new HashMap<>();
+ private final Object mWriteLock = new Object();
+
+ private String mAuthority = DEFAULT_AUTHORITY;
private SharedPreferences mPrefs;
- private Map<String, RootInfo> mRoots;
private String mSimulateReadErrors;
@Override
@@ -86,20 +92,18 @@
@VisibleForTesting
public void clearCacheAndBuildRoots() {
- final File cacheDir = getContext().getCacheDir();
- removeRecursively(cacheDir);
+ Log.d(TAG, "Resetting storage.");
+ removeChildrenRecursively(getContext().getCacheDir());
mStorage.clear();
mPrefs = getContext().getSharedPreferences(
"com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
Collection<String> rootIds = mPrefs.getStringSet("roots", null);
if (rootIds == null) {
- rootIds = Arrays.asList(new String[] {
- "sd0", "sd1"
- });
+ rootIds = Arrays.asList(new String[] { ROOT_0_ID, ROOT_1_ID });
}
- // Create new roots.
- mRoots = new HashMap<>();
+
+ mRoots.clear();
for (String rootId : rootIds) {
final RootInfo rootInfo = new RootInfo(rootId, getSize(rootId));
mRoots.put(rootId, rootInfo);
@@ -111,7 +115,7 @@
*/
private long getSize(String rootId) {
final String key = STORAGE_SIZE_KEY + "." + rootId;
- return mPrefs.getLong(key, DEFAULT_SIZE);
+ return mPrefs.getLong(key, DEFAULT_ROOT_SIZE);
}
@Override
@@ -125,7 +129,7 @@
row.add(Root.COLUMN_ROOT_ID, id);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, id);
- row.add(Root.COLUMN_DOCUMENT_ID, info.rootDocument.documentId);
+ row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
}
return result;
@@ -152,28 +156,48 @@
}
@Override
- public String createDocument(String parentDocumentId, String mimeType, String displayName)
+ public String createDocument(String parentId, String mimeType, String displayName)
throws FileNotFoundException {
- final StubDocument parentDocument = mStorage.get(parentDocumentId);
- if (parentDocument == null || !parentDocument.file.isDirectory()) {
- throw new FileNotFoundException();
+
+ final StubDocument parent = mStorage.get(parentId);
+ if (parent == null) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " in null parent.");
}
- final File file = new File(parentDocument.file, displayName);
- if (mimeType.equals(Document.MIME_TYPE_DIR)) {
- if (!file.mkdirs()) {
- throw new FileNotFoundException();
- }
- } else {
- try {
- if (!file.createNewFile()) {
- throw new IllegalStateException("The file " + file.getPath() + " already exists");
- }
- } catch (IOException e) {
- throw new FileNotFoundException();
- }
+ if (!parent.file.isDirectory()) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " inside non-directory parent "
+ + parent.file.getName());
}
- final StubDocument document = new StubDocument(file, mimeType, parentDocument);
+ final File file = new File(parent.file, displayName);
+ if (file.exists()) {
+ throw new FileNotFoundException(
+ "Duplicate file names not supported for " + file);
+ }
+
+ if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+ if (!file.mkdirs()) {
+ throw new FileNotFoundException(
+ "Failed to create directory(s): " + file);
+ }
+ Log.i(TAG, "Created new directory: " + file);
+ } else {
+ boolean created = false;
+ try {
+ created = file.createNewFile();
+ } catch (IOException e) {
+ // We'll throw an FNF exception later :)
+ Log.e(TAG, "createnewFile operation failed for file: " + file, e);
+ }
+ if (!created) {
+ throw new FileNotFoundException(
+ "createNewFile operation failed for: " + file);
+ }
+ Log.i(TAG, "Created new file: " + file);
+ }
+
+ final StubDocument document = new StubDocument(file, mimeType, parent);
Log.d(TAG, "Created document " + document.documentId);
notifyParentChanged(document.parentId);
getContext().getContentResolver().notifyChange(
@@ -349,7 +373,7 @@
private void configure(String arg, Bundle extras) {
Log.d(TAG, "Configure " + arg);
- String rootName = extras.getString(EXTRA_ROOT, MY_ROOT_ID);
+ String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
setSize(rootName, rootSize);
}
@@ -379,10 +403,10 @@
row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified());
}
- private void removeRecursively(File file) {
+ private void removeChildrenRecursively(File file) {
for (File childFile : file.listFiles()) {
if (childFile.isDirectory()) {
- removeRecursively(childFile);
+ removeChildrenRecursively(childFile);
}
childFile.delete();
}
@@ -411,8 +435,8 @@
@VisibleForTesting
public Uri createFile(String rootId, String path, String mimeType, byte[] content)
throws FileNotFoundException, IOException {
- Log.d(TAG, "Creating file " + rootId + ":" + path);
- StubDocument root = mRoots.get(rootId).rootDocument;
+ Log.d(TAG, "Creating test file " + rootId + ":" + path);
+ StubDocument root = mRoots.get(rootId).document;
if (root == null) {
throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
}
@@ -445,7 +469,7 @@
@VisibleForTesting
public File getFile(String rootId, String path) throws FileNotFoundException {
- StubDocument root = mRoots.get(rootId).rootDocument;
+ StubDocument root = mRoots.get(rootId).document;
if (root == null) {
throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
}
@@ -461,7 +485,7 @@
final class RootInfo {
public final String name;
- public final StubDocument rootDocument;
+ public final StubDocument document;
public long capacity;
public long size;
@@ -469,9 +493,11 @@
this.name = name;
this.capacity = 1024 * 1024;
// Make a subdir in the cache dir for each root.
- File rootDir = new File(getContext().getCacheDir(), name);
- rootDir.mkdir();
- this.rootDocument = new StubDocument(rootDir, Document.MIME_TYPE_DIR, this);
+ File file = new File(getContext().getCacheDir(), name);
+ if (file.mkdir()) {
+ Log.i(TAG, "Created new root directory @ " + file.getPath());
+ }
+ this.document = new StubDocument(file, Document.MIME_TYPE_DIR, this);
this.capacity = capacity;
this.size = 0;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
new file mode 100644
index 0000000..5c09794
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 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.documentsui;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A test helper class that provides support for controlling DocumentsUI activities
+ * programmatically, and making assertions against the state of the UI.
+ */
+class UiBot {
+
+ private static final String TAG = "UiBot";
+
+ private static final BySelector SNACK_DELETE =
+ By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
+
+ private UiDevice mDevice;
+ private int mTimeout;
+
+ public UiBot(UiDevice device, int timeout) {
+ mDevice = device;
+ mTimeout = timeout;
+ }
+
+ UiObject findRoot(String label) throws UiObjectNotFoundException {
+ final UiSelector rootsList = new UiSelector().resourceId(
+ "com.android.documentsui:id/container_roots").childSelector(
+ new UiSelector().resourceId("android:id/list"));
+
+ // We might need to expand drawer if not visible
+ if (!new UiObject(rootsList).waitForExists(mTimeout)) {
+ Log.d(TAG, "Failed to find roots list; trying to expand");
+ final UiSelector hamburger = new UiSelector().resourceId(
+ "com.android.documentsui:id/toolbar").childSelector(
+ new UiSelector().className("android.widget.ImageButton").clickable(true));
+ new UiObject(hamburger).click();
+ }
+
+ // Wait for the first list item to appear
+ new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+ // Now scroll around to find our item
+ new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+ return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+ }
+
+ void openRoot(String label) throws UiObjectNotFoundException {
+ findRoot(label).click();
+ mDevice.waitForIdle();
+ }
+
+ void assertHasRoots(String... labels) throws UiObjectNotFoundException {
+ List<String> missing = new ArrayList<>();
+ for (String label : labels) {
+ if (!findRoot(label).exists()) {
+ missing.add(label);
+ }
+ }
+ if (!missing.isEmpty()) {
+ Assert.fail(
+ "Expected roots " + Arrays.asList(labels) + ", but missing " + missing);
+ }
+ }
+
+ UiObject findDocument(String label) throws UiObjectNotFoundException {
+ final UiSelector docList = new UiSelector().resourceId(
+ "com.android.documentsui:id/container_directory").childSelector(
+ new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+ // Wait for the first list item to appear
+ new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+ // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+ return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+ }
+
+ boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
+ for (String label : labels) {
+ if (!findDocument(label).exists()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
+ List<String> missing = new ArrayList<>();
+ for (String label : labels) {
+ if (!findDocument(label).exists()) {
+ missing.add(label);
+ }
+ }
+ if (!missing.isEmpty()) {
+ Assert.fail(
+ "Expected documents " + Arrays.asList(labels) + ", but missing " + missing);
+ }
+ }
+
+ void clickDocument(String label) throws UiObjectNotFoundException {
+ findDocument(label).click();
+ }
+
+ void waitForDeleteSnackbar() {
+ mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
+ }
+
+ void waitForDeleteSnackbarGone() {
+ // wait a little longer for snackbar to go away, as it disappears after a timeout.
+ mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
+ }
+
+ void switchViewMode() {
+ UiObject2 mode = menuGridMode();
+ if (mode != null) {
+ mode.click();
+ } else {
+ menuListMode().click();
+ }
+ }
+
+ UiObject2 menuGridMode() {
+ // Note that we're using By.desc rather than By.res, because of b/25285770
+ return find(By.desc("Grid view"));
+ }
+
+ UiObject2 menuListMode() {
+ // Note that we're using By.desc rather than By.res, because of b/25285770
+ return find(By.desc("List view"));
+ }
+
+ UiObject2 menuDelete() {
+ return find(By.res("com.android.documentsui:id/menu_delete"));
+ }
+
+ private UiObject2 find(BySelector selector) {
+ mDevice.wait(Until.findObject(selector), mTimeout);
+ return mDevice.findObject(selector);
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index c44a7960..14eb084 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -231,4 +231,113 @@
<!-- Launch defaults preference summary with none set [CHAR LIMIT=40] -->
<string name="launch_defaults_none">No defaults set</string>
+ <!-- Text-To-Speech (TTS) settings --><skip />
+ <!-- Name of the TTS package as listed by the package manager. -->
+ <string name="tts_settings">Text-to-speech settings</string>
+ <!-- TTS option item name in the main settings screen -->
+ <string name="tts_settings_title">Text-to-speech output</string>
+ <!-- On main TTS Settings screen, in default settings section, setting default speech rate for synthesized voice -->
+ <string name="tts_default_rate_title">Speech rate</string>
+ <!-- On main TTS Settings screen, summary for default speech rate for synthesized voice -->
+ <string name="tts_default_rate_summary">Speed at which the text is spoken</string>
+ <!-- On main TTS Settings screen, in default settings section, setting default language for synthesized voice -->
+ <string name="tts_default_lang_title">Language</string>
+ <!-- Entry in the TTS engine language/locale picker, when selected will try to default to the system language [CHAR LIMIT=50] -->
+ <string name="tts_lang_use_system">Use system language</string>
+ <!-- On main TTS Settings screen, language summary if it can't default to system language [CHAR LIMIT=50] -->
+ <string name="tts_lang_not_selected">Language not selected</string>
+ <!-- On main TTS Settings screen, summary for default language for synthesized voice -->
+ <string name="tts_default_lang_summary">Sets the language-specific voice for the spoken text</string>
+ <!-- On main TTS Settings screen, triggers playback of an example of speech synthesis -->
+ <string name="tts_play_example_title">Listen to an example</string>
+ <!-- On main TTS Settings screen, summary for triggering playback of an example of speech synthesis -->
+ <string name="tts_play_example_summary">Play a short demonstration of speech synthesis</string>
+ <!-- On main TTS Settings screen, click to install required speech synthesis data -->
+ <string name="tts_install_data_title">Install voice data</string>
+ <!-- On main TTS Settings screen, summary for click to install required speech synthesis data -->
+ <string name="tts_install_data_summary">Install the voice data required for speech synthesis</string>
+ <!-- Warning message about security implications of enabling a TTS engine, displayed as a dialog
+ message when the user selects to enable an engine. -->
+ <string name="tts_engine_security_warning">This speech synthesis engine may be able to collect
+ all the text that will be spoken, including personal data like passwords and credit
+ card numbers. It comes from the <xliff:g id="tts_plugin_engine_name">%s</xliff:g> engine.
+ Enable the use of this speech synthesis engine?</string>
+ <!-- Warning message about required internet conectivity for TTS synthesis, displayed as a dialog
+ message when the user selects to play an example for network only locale and there's no internet connectivity. -->
+ <string name="tts_engine_network_required">This language requires a working network connection for text-to-speech output.</string>
+ <!-- Text spoken by the TTS engine as an example if the engine doesn't provide sample text [CHAR LIMIT=100] -->
+ <string name="tts_default_sample_string">This is an example of speech synthesis</string>
+ <!-- On main TTS Settings screen, title of a field explaining current TTS engine status for
+ current default language [CHAR LIMIT=50] -->
+ <string name="tts_status_title">Default language status</string>
+ <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+ selected language is fully supported by the engine [CHAR LIMIT=150]-->
+ <string name="tts_status_ok"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> is fully supported</string>
+ <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+ selected language is supported by the engine only if there's a working network connection [CHAR LIMIT=150] -->
+ <string name="tts_status_requires_network"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> requires network connection</string>
+ <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+ selected language is not supported by the engine [CHAR LIMIT=150] -->
+ <string name="tts_status_not_supported"><xliff:g id="locale" example="English (United States)">%1$s</xliff:g> is not supported</string>
+ <!-- On main TTS Settings screen, current TTS engine status for the current default language,
+ tts engine is queried for status [CHAR LIMIT=150] -->
+ <string name="tts_status_checking">Checking…</string>
+ <!-- Title for a preference in the main TTS settings screen, which
+ launches the settings screen for a given TTS engine when clicked
+ [CHAR LIMIT=30]-->
+ <string name="tts_engine_settings_title">Settings for <xliff:g id="tts_engine_name">%s</xliff:g></string>
+ <!-- [CHAR LIMIT=150] Text for screen readers / accessibility programs for
+ the image that launches the TTS engine settings when clicked. -->
+ <string name="tts_engine_settings_button">Launch engine settings</string>
+ <!-- [CHAR LIMIT=50] The text for the settings section that users to set a
+ preferred text to speech engine -->
+ <string name="tts_engine_preference_section_title">Preferred engine</string>
+ <!-- [CHAR LIMIT=50] Title of the settings section that displays general preferences
+ that are applicable to all engines, such as the speech rate -->
+ <string name="tts_general_section_title">General</string>
+
+ <!-- Default speech rate choices -->
+ <string-array name="tts_rate_entries">
+ <item>Very slow</item>
+ <item>Slow</item>
+ <item>Normal</item>
+ <item>Fast</item>
+ <item>Faster</item>
+ <item>Very fast</item>
+ <item>Rapid</item>
+ <item>Very rapid</item>
+ <item>Fastest</item>
+ </string-array>
+ <!-- Do not translate. -->
+ <string-array name="tts_rate_values">
+ <item>60</item>
+ <item>80</item>
+ <item>100</item>
+ <item>150</item>
+ <item>200</item>
+ <item>250</item>
+ <item>300</item>
+ <item>350</item>
+ <item>400</item>
+ </string-array>
+
+ <!-- Do not translate. -->
+ <string-array name="tts_demo_strings" translatable="false">
+ <item>This is an example of speech synthesis in English.</item>
+ <item>Voici un échantillon de synthèse vocale en français.</item>
+ <item>Dies ist ein Beispiel für Sprachsynthese in Deutsch.</item>
+ <item>Questo è un esempio di sintesi vocale in italiano.</item>
+ <item>Este es un ejemplo de síntesis de voz en español.</item>
+ <item>이것은 한국어 음성 합성의 예입니다.</item>
+ </string-array>
+ <!-- Do not translate. -->
+ <string-array name="tts_demo_string_langs" translatable="false">
+ <item>eng</item>
+ <item>fra</item>
+ <item>deu</item>
+ <item>ita</item>
+ <item>spa</item>
+ <item>kor</item>
+ </string-array>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index cf17ea5..99bd4ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -16,15 +16,20 @@
package com.android.settingslib.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.accessibility.AccessibilityManager;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -45,7 +50,7 @@
return Collections.emptySet();
}
- final Set<ComponentName> enabledServices = new HashSet<ComponentName>();
+ final Set<ComponentName> enabledServices = new HashSet<>();
final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
colonSplitter.setString(enabledServicesSetting);
@@ -71,4 +76,79 @@
final Context langContext = context.createConfigurationContext(config);
return langContext.getText(resId);
}
+
+ public static void setAccessibilityServiceState(Context context, ComponentName toggledService,
+ boolean enabled) {
+ // Parse the enabled services.
+ Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
+ context);
+
+ if (enabledServices.isEmpty()) {
+ enabledServices = new ArraySet<>(1);
+ }
+
+ // Determine enabled services and accessibility state.
+ boolean accessibilityEnabled = false;
+ if (enabled) {
+ enabledServices.add(toggledService);
+ // Enabling at least one service enables accessibility.
+ accessibilityEnabled = true;
+ } else {
+ enabledServices.remove(toggledService);
+ // Check how many enabled and installed services are present.
+ Set<ComponentName> installedServices = getInstalledServices(context);
+ for (ComponentName enabledService : enabledServices) {
+ if (installedServices.contains(enabledService)) {
+ // Disabling the last service disables accessibility.
+ accessibilityEnabled = true;
+ break;
+ }
+ }
+ }
+
+ // Update the enabled services setting.
+ StringBuilder enabledServicesBuilder = new StringBuilder();
+ // Keep the enabled services even if they are not installed since we
+ // have no way to know whether the application restore process has
+ // completed. In general the system should be responsible for the
+ // clean up not settings.
+ for (ComponentName enabledService : enabledServices) {
+ enabledServicesBuilder.append(enabledService.flattenToString());
+ enabledServicesBuilder.append(
+ AccessibilityUtils.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+ }
+ final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+ if (enabledServicesBuilderLength > 0) {
+ enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+ }
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ enabledServicesBuilder.toString());
+
+ // Update accessibility enabled.
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
+ }
+
+ private static Set<ComponentName> getInstalledServices(Context context) {
+ final Set<ComponentName> installedServices = new HashSet<>();
+ installedServices.clear();
+
+ final List<AccessibilityServiceInfo> installedServiceInfos =
+ AccessibilityManager.getInstance(context)
+ .getInstalledAccessibilityServiceList();
+ if (installedServiceInfos == null) {
+ return installedServices;
+ }
+
+ for (final AccessibilityServiceInfo info : installedServiceInfos) {
+ final ResolveInfo resolveInfo = info.getResolveInfo();
+ final ComponentName installedService = new ComponentName(
+ resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ installedServices.add(installedService);
+ }
+ return installedServices;
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index d7dfdf8..e28b2e5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -258,6 +258,9 @@
mScanId++;
final List<ScanResult> newResults = mWifiManager.getScanResults();
for (ScanResult newResult : newResults) {
+ if (newResult.SSID == null || newResult.SSID.isEmpty()) {
+ continue;
+ }
mScanResultCache.put(newResult.BSSID, newResult);
mSeenBssids.put(newResult.BSSID, mScanId);
}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index 0d1ad19..e90a3b5 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -136,9 +136,10 @@
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
// So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
// create the ClipData object with the attachments URIs.
- intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
- final ClipData clipData = new ClipData(
- null, new String[] { mimeType },
+ String messageBody = String.format("Build info: %s\nSerial number:%s",
+ SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
intent.setClipData(clipData);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 794e900..049754e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -19,12 +19,14 @@
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +36,7 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
@@ -620,4 +623,9 @@
int getOffsetTop(TileRecord tile);
void updateResources();
}
+
+ public static boolean isTheNewQS(Context context) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(), QS_THE_NEW_QS,
+ ActivityManager.getCurrentUser(), 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 61695b2..48b74a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,6 +28,7 @@
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
@@ -93,7 +94,7 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = !mKeyguard.isSecure() || !mKeyguard.isShowing()
- || mKeyguard.canSkipBouncer();
+ || mKeyguard.canSkipBouncer() || QSPanel.isTheNewQS(mContext);
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.value = false;
state.autoMirrorDrawable = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c6fc6ff..2f9a496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -21,6 +21,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.UsageTracker;
@@ -110,7 +111,7 @@
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
- state.visible = enabled || mUsageTracker.isRecentlyUsed();
+ state.visible = enabled || mUsageTracker.isRecentlyUsed() || QSPanel.isTheNewQS(mContext);
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
state.icon = enabled ? mEnable : mDisable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 7b83e6a..79084ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -23,6 +23,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -88,7 +89,8 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
+ state.visible = (mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed())
+ || QSPanel.isTheNewQS(mContext);
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
if (arg instanceof Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index e6fade4..0e2672c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -18,6 +18,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
@@ -73,7 +74,7 @@
// Work around for bug 15916487: don't show location tile on top of lock screen. After the
// bug is fixed, this should be reverted to only hiding it on secure lock screens:
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
- state.visible = !mKeyguard.isShowing();
+ state.visible = !mKeyguard.isShowing() || QSPanel.isTheNewQS(mContext);
state.value = locationEnabled;
if (locationEnabled) {
state.icon = mEnable;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 4d40cb7..a58bc58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -21,9 +21,11 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -52,10 +54,21 @@
public final static int EVENT_BUS_PRIORITY = 1;
public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
+ // Purely for experimentation
+ private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
+ private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
+ private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
+ private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
+
private static SystemServicesProxy sSystemServicesProxy;
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
+ // For experiments only, allows another package to handle recents if it is defined in the system
+ // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager,
+ // and does not reside in the home stack.
+ private String mOverrideRecentsPackageName;
+
private Handler mHandler;
private RecentsImpl mImpl;
@@ -142,6 +155,14 @@
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
+ // Check if there is a recents override package
+ if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) {
+ String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
+ if (!cnStr.isEmpty()) {
+ mOverrideRecentsPackageName = cnStr;
+ }
+ }
+
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
@@ -172,6 +193,10 @@
*/
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+ if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.showRecents(triggeredFromAltTab);
@@ -197,6 +222,10 @@
*/
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -222,6 +251,10 @@
*/
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+ if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents();
@@ -421,4 +454,19 @@
}
mOnConnectRunnables.clear();
}
+
+ /**
+ * Attempts to proxy the following action to the override recents package.
+ * @return whether the proxying was successful
+ */
+ private boolean proxyToOverridePackage(String action) {
+ if (mOverrideRecentsPackageName != null) {
+ Intent intent = new Intent(action);
+ intent.setPackage(mOverrideRecentsPackageName);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4d575bc..b5a1a93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -517,6 +517,15 @@
boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
mLastTabKeyEventTime) > altTabKeyDelay;
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
+ // As we iterate to the next/previous task, cancel any current/lagging window
+ // transition animations
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ if (launchState.launchedToTaskId != -1) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.cancelWindowTransition(launchState.launchedToTaskId);
+ }
+
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
if (backward) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 243c0e1..0e11f02 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.recents;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import android.app.ActivityManager;
import android.app.ActivityOptions;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 28299d3..d415845 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -33,8 +33,8 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.RecentsView;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
/**
* A helper for the dialogs that show when task debugging is on.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
index ea6821f..515c3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -189,6 +189,7 @@
public float computePOffsetForScaledHeight(int height, Rect bounds) {
int top = bounds.top;
int bottom = bounds.bottom;
+ height = Math.min(height, bottom - top);
if (bounds.height() == 0) {
return 0;
@@ -231,6 +232,7 @@
public float computePOffsetForHeight(int height, Rect bounds) {
int top = bounds.top;
int bottom = bounds.bottom;
+ height = Math.min(height, bottom - top);
if (bounds.height() == 0) {
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 141562c..2fe5e98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -57,6 +57,7 @@
import android.util.Pair;
import android.view.Display;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
@@ -71,8 +72,10 @@
import java.util.List;
import java.util.Random;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -232,6 +235,15 @@
return null;
}
+ /**
+ * Returns whether this device has freeform workspaces.
+ */
+ public boolean hasFreeformWorkspaceSupport() {
+ if (mPm == null) return false;
+
+ return mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ }
+
/** Returns whether the recents is currently running */
public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
MutableBoolean isHomeTopMost) {
@@ -310,14 +322,14 @@
* Returns whether the given stack id is the home stack id.
*/
public static boolean isHomeStack(int stackId) {
- return stackId == ActivityManager.HOME_STACK_ID;
+ return stackId == HOME_STACK_ID;
}
/**
* Returns whether the given stack id is the freeform workspace stack id.
*/
public static boolean isFreeformStack(int stackId) {
- return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
}
/**
@@ -335,6 +347,19 @@
return stackInfo != null;
}
+ /**
+ * Cancels the current window transtion to/from Recents for the given task id.
+ */
+ public void cancelWindowTransition(int taskId) {
+ if (mWm == null) return;
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().cancelTaskWindowTransition(taskId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
/** Returns the top task thumbnail for the given task id */
public Bitmap getTaskThumbnail(int taskId) {
if (mAm == null) return null;
@@ -714,7 +739,7 @@
try {
// Use the home stack bounds
- ActivityManager.StackInfo stackInfo = mIam.getStackInfo(ActivityManager.HOME_STACK_ID);
+ ActivityManager.StackInfo stackInfo = mIam.getStackInfo(HOME_STACK_ID);
if (stackInfo != null) {
windowRect.set(stackInfo.bounds);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6c83b87..62493d6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -133,9 +133,11 @@
Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
- iconFilename);
+ iconFilename, t.bounds);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
- if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
+ if (DEBUG) {
+ Log.d(TAG, activityLabel + " bounds: " + t.bounds);
+ }
if (task.isFreeformTask()) {
freeformTasks.add(task);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 71e3957..bba453a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -560,6 +560,11 @@
ActivityInfo activityInfo = mActivityInfoCache.get(cn);
if (activityInfo == null) {
activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+ if (cn == null || activityInfo == null) {
+ Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
+ activityInfo);
+ return null;
+ }
mActivityInfoCache.put(cn, activityInfo);
}
return activityInfo;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 60051b8..51a1ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.android.systemui.recents.misc.Utilities;
@@ -105,6 +106,7 @@
public boolean lockToTaskEnabled;
public Bitmap icon;
public String iconFilename;
+ public Rect bounds;
private TaskCallbacks mCb;
@@ -115,7 +117,7 @@
public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
String activityTitle, String contentDescription, Drawable activityIcon,
int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
- String iconFilename) {
+ String iconFilename, Rect bounds) {
boolean isInAffiliationGroup = (taskAffiliation != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
this.key = key;
@@ -132,6 +134,7 @@
this.lockToTaskEnabled = lockToTaskEnabled;
this.icon = icon;
this.iconFilename = iconFilename;
+ this.bounds = bounds;
}
/** Copies the other task. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
index 6d11f14..67a6a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.model;
+import android.util.Log;
import android.util.LruCache;
import android.util.SparseArray;
@@ -29,6 +30,8 @@
*/
public class TaskKeyLruCache<V> {
+ private static final String TAG = "TaskKeyLruCache";
+
private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
private final LruCache<Integer, V> mCache;
@@ -71,6 +74,10 @@
/** Puts an entry in the cache for a specific key. */
final void put(Task.TaskKey key, V value) {
+ if (key == null || value == null) {
+ Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
+ return;
+ }
mKeys.put(key.id, key);
mCache.put(key.id, value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index ce993c5..a97a2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -17,10 +17,11 @@
package com.android.systemui.recents.views;
import android.util.Log;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* The layout logic for the contents of the freeform workspace.
@@ -33,6 +34,7 @@
// The number of cells in the freeform workspace
private int mFreeformCellXCount;
private int mFreeformCellYCount;
+
// The width and height of the cells in the freeform workspace
private int mFreeformCellWidth;
private int mFreeformCellHeight;
@@ -44,22 +46,26 @@
* Updates the layout for each of the freeform workspace tasks. This is called after the stack
* layout is updated.
*/
- public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ mTaskIndexMap.clear();
+
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
- mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
- mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+ mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+ mFreeformCellXCount));
// For now, make the cells square
+ mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
+ stackLayout.mFreeformRect.height() / mFreeformCellYCount);
mFreeformCellHeight = mFreeformCellWidth;
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
int taskCount = freeformTasks.size();
- for (int i = 0; i < taskCount; i++) {
+ for (int i = taskCount - 1; i >= 0; i--) {
Task task = freeformTasks.get(i);
- mTaskIndexMap.put(task.key, i);
+ mTaskIndexMap.put(task.key, taskCount - i - 1);
}
if (DEBUG) {
@@ -74,24 +80,23 @@
/**
* Returns whether the transform is available for the given task.
*/
- public boolean isTransformAvailable(Task task, float stackScroll,
- TaskStackLayoutAlgorithm stackLayout) {
- if (stackLayout.mNumFreeformTasks == 0 || task == null ||
- !mTaskIndexMap.containsKey(task.key)) {
+ public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
+ if (stackLayout.mNumFreeformTasks == 0 || task == null) {
return false;
}
- return stackScroll > stackLayout.mStackEndScrollP;
+ return mTaskIndexMap.containsKey(task.key);
}
/**
* Returns the transform for the given task. Any rect returned will be offset by the actual
* transform for the freeform workspace.
*/
- public TaskViewTransform getTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
- if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+ public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
+ TaskStackLayoutAlgorithm stackLayout) {
+ if (mTaskIndexMap.containsKey(task.key)) {
// This is a freeform task, so lay it out in the freeform workspace
int taskIndex = mTaskIndexMap.get(task.key);
+ int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
@@ -99,8 +104,13 @@
int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
transformOut.scale = scale * 0.9f;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
- transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+ transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
+ transformOut.translationZ = stackLayout.mMaxTranslationZ;
+ transformOut.rect.set(stackLayout.mTaskRect);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
+ transformOut.p = 0;
return transformOut;
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 422e917..d72e50e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -44,6 +44,7 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
@@ -60,7 +61,9 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -488,14 +491,14 @@
int destinationStack) {
final int targetStackId = destinationStack != INVALID_STACK_ID ?
destinationStack : clickedTask.getTask().key.stackId;
- if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
- && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
+ && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
return null;
}
// If this is a full screen stack, the transition will be towards the single, full screen
// task. We only need the transition spec for this task.
List<AppTransitionAnimationSpec> specs = new ArrayList<>();
- if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
specs.add(createThumbnailHeaderAnimationSpec(
stackView, offsetX, offsetY, stackScroll, clickedTask,
clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
@@ -587,6 +590,20 @@
return new AppTransitionAnimationSpec(taskId, b, rect);
}
+ /**
+ * Cancels any running window transitions for the launched task (the task animating into
+ * Recents).
+ */
+ private void cancelLaunchedTaskWindowTransition(final Task task) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ if (launchState.launchedToTaskId != -1 &&
+ launchState.launchedToTaskId != task.key.id) {
+ ssp.cancelWindowTransition(launchState.launchedToTaskId);
+ }
+ }
+
/**** TaskStackView.TaskStackCallbacks Implementation ****/
@Override
@@ -617,34 +634,37 @@
// Compute the thumbnail to scale up from
final SystemServicesProxy ssp = Recents.getSystemServices();
+ boolean screenPinningRequested = false;
ActivityOptions opts = null;
ActivityOptions.OnAnimationStartedListener animStartedListener = null;
if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
task.thumbnail.getHeight() > 0) {
- if (lockToTask) {
- animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
- boolean mTriggered = false;
- @Override
- public void onAnimationStarted() {
- if (!mTriggered) {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- EventBus.getDefault().send(new ScreenPinningRequestEvent(
- getContext(), ssp));
- }
- }, 350);
- mTriggered = true;
- }
+ animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+ @Override
+ public void onAnimationStarted() {
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ cancelLaunchedTaskWindowTransition(task);
+
+ if (lockToTask) {
+ // Request screen pinning after the animation runs
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(
+ getContext(), ssp));
+ }
+ }, 350);
}
- };
- }
+ }
+ };
postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
animStartedListener, destinationStack);
opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
sourceView.getHandler(), animStartedListener);
+ screenPinningRequested = true;
} else {
opts = ActivityOptions.makeBasic();
}
@@ -652,7 +672,7 @@
opts.setBounds(bounds.isEmpty() ? null : bounds);
}
final ActivityOptions launchOpts = opts;
- final boolean screenPinningRequested = (animStartedListener == null) && lockToTask;
+ final boolean finalScreenPinningRequested = screenPinningRequested;
final Runnable launchRunnable = new Runnable() {
@Override
public void run() {
@@ -660,9 +680,11 @@
// Bring an active task to the foreground
ssp.moveTaskToFront(task.key.id, launchOpts);
} else {
- if (ssp.startActivityFromRecents(getContext(), task.key.id,
- task.activityLabel, launchOpts)) {
- if (screenPinningRequested) {
+ if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
+ launchOpts)) {
+ if (!finalScreenPinningRequested) {
+ // If we have not requested this already to be run after the window
+ // transition, then just run it now
EventBus.getDefault().send(new ScreenPinningRequestEvent(
getContext(), ssp));
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 45d626e..a0713d7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,12 +24,14 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
/**
@@ -44,14 +46,8 @@
private static final float STACK_PEEK_MIN_SCALE = 0.85f;
// The scale of the last task
private static final float SINGLE_TASK_SCALE = 0.95f;
- // The percentage of the height of the stack that we want to show the last task at
- private static final float VISIBLE_LAST_TASK_HEIGHT_PCT = 0.45f;
// The percentage of height of task to show between tasks
private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
- // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
- // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
- // workspace)
- private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -67,14 +63,36 @@
Context mContext;
- // This is the view bounds inset exactly by the search bar, but without the bottom inset
- // see RecentsConfiguration.getTaskStackBounds()
- public Rect mStackRect = new Rect();
- // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
- // of the stack rect
+ /*
+ +-------------------+
+ | SEARCH |
+ +-------------------+
+ |+-----------------+|
+ || FREEFORM ||
+ || ||
+ || ||
+ |+-----------------+|
+ | +-----------+ |
+ | +---------------+ |
+ | | | |
+ |+-----------------+|
+ || STACK ||
+ +-------------------+
+ */
+
+ // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
- // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
+ // The freeform workspace bounds, inset from the top by the search bar, and is a fixed height
public Rect mFreeformRect = new Rect();
+ // The freeform stack bounds, inset from the top by the search bar and freeform workspace, and
+ // runs to the bottom of the screen
+ private Rect mFreeformStackRect = new Rect();
+ // The stack bounds, inset from the top by the search bar, and runs to
+ // the bottom of the screen
+ private Rect mStackRect = new Rect();
+ // The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
+ // there is a freeform workspace
+ public Rect mCurrentStackRect;
// This is the current system insets
public Rect mSystemInsets = new Rect();
@@ -83,13 +101,6 @@
// The largest scroll progress, at this value, the front most task will be visible above the
// navigation bar
float mMaxScrollP;
- // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
- // of the stack
- float mStackEndScrollP;
- // The scroll progress that we actually want to scroll the user to when they want to go to the
- // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
- // the bottom of the stack)
- float mPreferredStackEndScrollP;
// The initial progress that the scroller is set when you first enter recents
float mInitialScrollP;
// The task progress for the front-most task in the stack
@@ -109,13 +120,6 @@
// The relative progress to ensure that the offset from the bottom of the stack to the bottom
// of the task is respected
float mStackBottomPOffset;
- // The freeform workspace gap
- int mFreeformWorkspaceGapOffset;
- float mFreeformWorkspaceGapPOffset;
- // The relative progress to ensure that the freeform workspace height + gap + stack bottom
- // padding is respected
- int mFreeformWorkspaceOffset;
- float mFreeformWorkspacePOffset;
// The last computed task counts
int mNumStackTasks;
@@ -130,9 +134,6 @@
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
- // Temporary task view transform
- TaskViewTransform mTmpTransform = new TaskViewTransform();
-
// Log function
static ParametricCurve sCurve;
@@ -190,53 +191,57 @@
}
/**
- * Computes the stack and task rects.
+ * Computes the stack and task rects. The given task stack bounds is the whole bounds not
+ * including the search bar.
*/
public void initialize(Rect taskStackBounds) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_stack_top_padding);
- // Compute the stack rect, inset from the given task stack bounds
- mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
- taskStackBounds.right - widthPadding, taskStackBounds.bottom);
+ // The freeform height is the visible height (not including system insets) - padding above
+ // freeform and below stack - gap between the freeform and stack
mStackBottomOffset = mSystemInsets.bottom + heightPadding;
-
- // Compute the task rect, align it to the top-center square in the stack rect
+ int ffHeight = (taskStackBounds.height() - 2 * heightPadding - mStackBottomOffset) / 2;
+ mFreeformRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.top + heightPadding + ffHeight);
+ mFreeformStackRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding + ffHeight + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.bottom);
+ mStackRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.bottom);
+ // Anchor the task rect to the top-center of the non-freeform stack rect
int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
- int xOffset = (mStackRect.width() - size) / 2;
- mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
- mStackRect.right - xOffset, mStackRect.top + size);
-
- // Compute the freeform rect, align it to the top-left of the stack rect
- mFreeformRect.set(mStackRect);
- mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
+ mTaskRect.set(mStackRect.left, mStackRect.top,
+ mStackRect.left + size, mStackRect.top + size);
+ mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
// Compute the progress offsets
int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
- mStackRect);
+ mCurrentStackRect);
mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
- mStackRect);
+ mCurrentStackRect);
mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
- mStackRect);
+ mCurrentStackRect);
mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
- mStackRect);
- mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
- mFreeformWorkspaceGapOffset = mStackBottomOffset;
- mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
- mStackRect);
- mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
- mStackBottomOffset;
- mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
- mStackRect);
+ mCurrentStackRect);
+ mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
if (DEBUG) {
Log.d(TAG, "initialize");
Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
+ Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
+ Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
@@ -246,20 +251,9 @@
Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
- Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
- Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
- Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
- Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
-
- for (int height = 0; height <= 2000; height += 50) {
- float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
- float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
- Log.d(TAG, "offset: " + height + ", " +
- p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
- sCurve.pToScale(1f - p) + ", " +
- p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
- }
+ Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
+ Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
}
}
@@ -279,7 +273,7 @@
ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
- mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
+ mMinScrollP = mMaxScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
@@ -307,6 +301,10 @@
Task task = stackTasks.get(i);
mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+ if (DEBUG) {
+ Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
+ }
+
if (i < (taskCount - 1)) {
// Increment the peek height
float pPeek = task.group.isFrontMostTask(task) ?
@@ -316,37 +314,24 @@
}
mFrontMostTaskP = pAtFrontMostTaskTop;
- // Set the stack end scroll progress to the point at which the bottom of the front-most
- // task is aligned to the bottom of the stack
- mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop, mTaskHeightPOffset);
if (mNumStackTasks > 1) {
- // Set the preferred stack end scroll progress to the point where the bottom of the
- // front-most task is offset by the navbar and padding from the bottom of the stack
- mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
-
+ // Set the stack end scroll progress to the point at which the bottom of the front-most
+ // task is aligned to the bottom of the stack
+ mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
// Basically align the back-most task such that the last two tasks would be visible
- mMinScrollP = alignToStackBottom(pAtBackMostTaskTop, 2 *
- mBetweenAffiliationPOffset);
+ mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
} else {
// When there is a single item, then just make all the stack progresses the same
- mPreferredStackEndScrollP = mStackEndScrollP;
- mMinScrollP = mStackEndScrollP;
+ mMinScrollP = mMaxScrollP = 0;
}
- } else {
- // TODO: In the case where there is only freeform tasks, then the scrolls should be
- // set to zero
}
if (!freeformTasks.isEmpty()) {
- // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
- // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
- // workspace will be visible
mFreeformLayoutAlgorithm.update(freeformTasks, this);
- mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
- mInitialScrollP = isInitialStateFreeform(stack) ?
- mMaxScrollP : mPreferredStackEndScrollP;
+ mInitialScrollP = mMaxScrollP;
} else {
- mMaxScrollP = mPreferredStackEndScrollP;
mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
}
@@ -354,7 +339,6 @@
Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
Log.d(TAG, "mMinScrollP: " + mMinScrollP);
- Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
}
}
@@ -369,28 +353,28 @@
return new VisibilityReport(1, 1);
}
- // If there are freeform tasks, then they will be the only ones visible
- int freeformTaskCount = 0;
- for (Task t : tasks) {
- if (t.isFreeformTask()) {
- freeformTaskCount++;
- }
- }
- if (freeformTaskCount > 0) {
- return new VisibilityReport(freeformTaskCount, freeformTaskCount);
+ // Quick return when there are no stack tasks
+ if (mNumStackTasks == 0) {
+ return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
+ Math.max(mNumFreeformTasks, 1));
}
// Otherwise, walk backwards in the stack and count the number of tasks and visible
- // thumbnails
+ // thumbnails and add that to the total freeform task count
int taskHeight = mTaskRect.height();
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
- int numVisibleTasks = 1;
- int numVisibleThumbnails = 1;
- float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
- int prevScreenY = sCurve.pToX(progress, mStackRect);
+ int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
+ int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
+ Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
+ float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
+ int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
for (int i = tasks.size() - 2; i >= 0; i--) {
Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ continue;
+ }
+
progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
if (progress < 0) {
break;
@@ -399,7 +383,7 @@
if (isFrontMostTaskInGroup) {
float scaleAtP = sCurve.pToScale(progress);
int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
- int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
+ int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
@@ -431,17 +415,8 @@
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
- mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
- if (transformOut.visible) {
- getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
- transformOut.translationY += mTmpTransform.translationY;
- transformOut.translationZ = mMaxTranslationZ;
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.p = 0;
- }
+ if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
+ mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
} else {
// Return early if we have an invalid index
@@ -463,32 +438,25 @@
// modulate some values directly
float pTaskRelative = mMinScrollP - stackScroll;
float scale = (mNumFreeformTasks > 0) ? 1f : SINGLE_TASK_SCALE;
- int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
+ int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
+ (mCurrentStackRect.height() - mTaskRect.height()) / 2;
transformOut.scale = scale;
- transformOut.translationX = 0;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+ transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
return transformOut;
} else {
- // Once we scroll past the preferred stack end scroll, then we should start translating
- // the cards in screen space and lock their final state at the end stack progress
- int overscrollYOffset = 0;
- if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
- float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
- (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
- overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
- (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
- stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
- }
-
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+ if (DEBUG) {
+ Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+ }
// If the task top is outside of the bounds below the screen, then immediately reset it
if (pTaskRelative > 1f) {
@@ -508,18 +476,18 @@
float scale = sCurve.pToScale(pBounded);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
- transformOut.translationX = 0;
- transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
- scaleYOffset - overscrollYOffset;
+ transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
+ (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
+ scaleYOffset;
transformOut.translationZ = Math.max(mMinTranslationZ,
mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
if (DEBUG) {
- Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
Log.d(TAG, "\t" + transformOut);
}
@@ -528,61 +496,6 @@
}
/**
- * Returns whether this stack should be initialized to show the freeform workspace or not.
- */
- public boolean isInitialStateFreeform(TaskStack stack) {
- Task launchTarget = stack.getLaunchTarget();
- if (launchTarget != null) {
- return launchTarget.isFreeformTask();
- }
- Task frontTask = stack.getFrontMostTask();
- if (frontTask != null) {
- return frontTask.isFreeformTask();
- }
- return false;
- }
-
- /**
- * Update/get the transform
- */
- public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
- TaskViewTransform transformOut) {
- transformOut.reset();
- if (mNumFreeformTasks == 0) {
- return transformOut;
- }
-
- if (stackScroll > mStackEndScrollP) {
- // mStackEndScroll is the point at which the first stack task is bottom aligned with the
- // stack, so we offset from on the stack rect height.
- float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
- mFreeformWorkspacePOffset;
- int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
- transformOut.scale = 1f;
- transformOut.alpha = 1f;
- transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
- overscrollYOffset;
- transformOut.rect.set(mFreeformRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- }
- return transformOut;
- }
-
- /**
- * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
- */
- public float getPreferredMaxScrollPosition(float scroll) {
- float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
- (mMaxScrollP - mStackEndScrollP);
- if (scroll < maxStackScrollBounds) {
- return mPreferredStackEndScrollP;
- }
- return mMaxScrollP;
- }
-
- /**
* Returns the untransformed task view bounds.
*/
public Rect getUntransformedTaskViewBounds() {
@@ -604,7 +517,7 @@
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
- float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
+ float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
return -deltaP;
}
@@ -613,7 +526,7 @@
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
- int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
+ int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
return -y;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b266eaa..14d75a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -58,7 +58,7 @@
import java.util.Iterator;
import java.util.List;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/* The visual representation of a task stack view */
@@ -281,6 +281,7 @@
/**
* Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
+ * This call ignores freeform tasks.
*/
private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
ArrayList<Task> tasks,
@@ -306,8 +307,16 @@
// Update the stack transforms
TaskViewTransform prevTransform = null;
for (int i = taskCount - 1; i >= 0; i--) {
- TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i),
- stackScroll, taskTransforms.get(i), prevTransform);
+ Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ continue;
+ }
+
+ TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+ taskTransforms.get(i), prevTransform);
+ if (DEBUG) {
+ Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
+ }
if (transform.visible) {
if (frontMostVisibleIndex < 0) {
frontMostVisibleIndex = i;
@@ -327,7 +336,7 @@
if (boundTranslationsToRect) {
transform.translationY = Math.min(transform.translationY,
- mLayoutAlgorithm.mStackRect.bottom);
+ mLayoutAlgorithm.mCurrentStackRect.bottom);
}
prevTransform = transform;
}
@@ -344,13 +353,13 @@
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
float stackScroll = mStackScroller.getStackScroll();
- int[] visibleRange = mTmpVisibleRange;
- boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
- stackScroll, visibleRange, false);
+ int[] visibleStackRange = mTmpVisibleRange;
+ boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
+ stackScroll, visibleStackRange, false);
boolean hasStackBackTransform = false;
boolean hasStackFrontTransform = false;
if (DEBUG) {
- Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+ Log.d(TAG, "visibleRange: " + visibleStackRange[0] + " to " + visibleStackRange[1]);
}
// Return all the invisible children to the pool
@@ -363,7 +372,8 @@
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfTask(task);
- if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
+ if (task.isFreeformTask() ||
+ visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
if (tv.isFocusedTask()) {
@@ -371,16 +381,43 @@
lastFocusedTaskIndex = taskIndex;
resetFocusedTask();
}
+ if (DEBUG) {
+ Log.d(TAG, "returning to pool: " + task.key);
+ }
mViewPool.returnViewToPool(tv);
}
}
+ // Pick up all the freeform tasks
+ int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0;
+ for (int i = mStack.getTaskCount() - 1; i > firstVisStackIndex; i--) {
+ Task task = tasks.get(i);
+ if (!task.isFreeformTask()) {
+ continue;
+ }
+ TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+ mCurrentTaskTransforms.get(i), null);
+ TaskView tv = mTmpTaskViewMap.get(task);
+ if (tv == null) {
+ if (DEBUG) {
+ Log.d(TAG, "picking up from pool: " + task.key);
+ }
+ tv = mViewPool.pickUpViewFromPool(task, task);
+ if (mLayersDisabled) {
+ tv.disableLayersForOneFrame();
+ }
+ }
+
+ // Animate the task into place
+ tv.updateViewPropertiesToTaskTransform(transform,
+ mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+ }
+
// Pick up all the newly visible children and update all the existing children
- for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
+ for (int i = visibleStackRange[0]; isValidVisibleStackRange && i >= visibleStackRange[1]; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = mCurrentTaskTransforms.get(i);
TaskView tv = mTmpTaskViewMap.get(task);
- int taskIndex = mStack.indexOfTask(task);
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
@@ -409,29 +446,19 @@
}
// Animate the task into place
- tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
+ tv.updateViewPropertiesToTaskTransform(transform,
mStackViewsAnimationDuration, mRequestUpdateClippingListener);
}
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
- if (lastFocusedTaskIndex < visibleRange[1]) {
- setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+ if (lastFocusedTaskIndex < visibleStackRange[1]) {
+ setFocusedTask(visibleStackRange[1], false, wasLastFocusedTaskAnimated);
} else {
- setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
+ setFocusedTask(visibleStackRange[0], false, wasLastFocusedTaskAnimated);
}
}
- // Update the freeform workspace
- mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
- if (mTmpTransform.visible) {
- mTmpTransform.rect.roundOut(mTmpRect);
- mFreeformWorkspaceBackground.setAlpha(255);
- mFreeformWorkspaceBackground.setBounds(mTmpRect);
- } else {
- mFreeformWorkspaceBackground.setAlpha(0);
- }
-
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
@@ -491,6 +518,14 @@
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack);
+ // Update the freeform workspace
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
+ mFreeformWorkspaceBackground.setAlpha(255);
+ mFreeformWorkspaceBackground.setBounds(mTmpRect);
+ }
+
// Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
@@ -762,7 +797,7 @@
/** Handler for the first layout. */
void onFirstLayout() {
- int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
+ int offscreenY = mLayoutAlgorithm.mCurrentStackRect.bottom;
// Find the launch target task
Task launchTargetTask = mStack.getLaunchTarget();
@@ -863,7 +898,7 @@
mStackScroller.stopScroller();
mStackScroller.stopBoundScrollAnimation();
// Animate all the task views out of view
- ctx.offscreenTranslationY = mLayoutAlgorithm.mStackRect.bottom;
+ ctx.offscreenTranslationY = mLayoutAlgorithm.mCurrentStackRect.bottom;
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
@@ -929,6 +964,18 @@
}
}
+ /**
+ * Launches the freeform tasks.
+ */
+ public boolean launchFreeformTasks() {
+ Task frontTask = mStack.getFrontMostTask();
+ if (frontTask != null && frontTask.isFreeformTask()) {
+ onTaskViewClicked(getChildViewForTask(frontTask), frontTask, false);
+ return true;
+ }
+ return false;
+ }
+
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -975,6 +1022,19 @@
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
+ } else {
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
+
+ // Update the min/max scroll and animate other task views into their new positions
+ updateMinMaxScroll(true);
+
+ // Animate all the tasks into place
+ requestSynchronizeStackViewsWithModel(200);
}
// Update the new front most task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 6b92aed..3a2ed0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -122,7 +122,7 @@
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP,
- Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
+ Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
}
/** Returns the amount that the absolute value of how much the scroll is out of bounds. */
@@ -194,7 +194,7 @@
// TODO: Remove
@Deprecated
int progressToScrollRange(float p) {
- return (int) (p * mLayoutAlgorithm.mStackRect.height());
+ return (int) (p * mLayoutAlgorithm.mCurrentStackRect.height());
}
/** Called from the view draw, computes the next scroll. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 9e6fb7b..59c9708 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -19,6 +19,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -29,9 +30,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -222,49 +225,12 @@
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
- float curScrollP = mScroller.getStackScroll();
if (mIsScrolling) {
- boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
- if (hasFreeformTasks && velocity > 0 &&
- curScrollP > layoutAlgorithm.mStackEndScrollP) {
- // Snap to workspace
- float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mPreferredStackEndScrollP);
- mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
- mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
- (Integer) animation.getAnimatedValue());
- float scroll = mDownScrollP + deltaP;
- mScroller.setStackScroll(scroll);
- }
- });
- mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
- mScrollFlingAnimator.start();
- } else if (hasFreeformTasks && velocity < 0 &&
- curScrollP > (layoutAlgorithm.mStackEndScrollP -
- layoutAlgorithm.mTaskHalfHeightPOffset)) {
- // Snap to stack
- float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mMaxScrollP);
- mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
- mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
- (Integer) animation.getAnimatedValue());
- float scroll = mDownScrollP + deltaP;
- mScroller.setStackScroll(scroll);
- }
- });
- mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
- mScrollFlingAnimator.start();
- } else if (mScroller.isScrollOutOfBounds()) {
+ if (mScroller.isScrollOutOfBounds()) {
mScroller.animateBoundScroll();
} else if (Math.abs(velocity) > mMinimumVelocity) {
float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mPreferredStackEndScrollP);
+ layoutAlgorithm.mMaxScrollP);
float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMinScrollP);
mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
@@ -313,6 +279,17 @@
return;
}
+ // If tapping on the freeform workspace background, just launch the first freeform task
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
+ if (freeformRect.top <= y && y <= freeformRect.bottom) {
+ if (mSv.launchFreeformTasks()) {
+ return;
+ }
+ }
+ }
+
// The user intentionally tapped on the background, which is like a tap on the "desktop".
// Hide recents and transition to the launcher.
EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 1e038c8..3f0000e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -124,7 +124,7 @@
private static final boolean NOTIFICATION_CLICK_DEBUG = true;
public static final boolean ENABLE_REMOTE_INPUT =
- Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false);
+ SystemProperties.getBoolean("debug.enable_remote_input", true);
public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
&& SystemProperties.getBoolean("debug.child_notifs", false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 4ac2c31..a51f62a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import android.animation.LayoutTransition;
import android.annotation.Nullable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index ebe7785..fafedc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -128,7 +128,6 @@
};
protected void onExpandingFinished() {
- endClosing();
mBar.onExpandingFinished();
}
@@ -143,6 +142,7 @@
}
protected final void notifyExpandingFinished() {
+ endClosing();
if (mExpanding) {
mExpanding = false;
onExpandingFinished();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 749a080..535a8ef 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -91,7 +91,6 @@
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlPullParserException;
@@ -180,6 +179,8 @@
private final MainHandler mMainHandler;
+ private MagnificationController mMagnificationController;
+
private InteractionBridge mInteractionBridge;
private AlertDialog mEnableTouchExplorationDialog;
@@ -1936,6 +1937,13 @@
}
}
+ MagnificationController getMagnificationController() {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(mContext, this);
+ }
+ return mMagnificationController;
+ }
+
/**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
new file mode 100644
index 0000000..b4411cf
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2015 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.accessibility;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Property;
+import android.util.Slog;
+import android.view.MagnificationSpec;
+import android.view.WindowManagerInternal;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This class is used to control and query the state of display magnification
+ * from the accessibility manager and related classes. It is responsible for
+ * holding the current state of magnification and animation, and it handles
+ * communication between the accessibility manager and window manager.
+ */
+class MagnificationController {
+ private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+
+ private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
+ private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
+
+ private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+
+ private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+ private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
+
+ private final Region mMagnifiedBounds = new Region();
+ private final Rect mTempRect = new Rect();
+
+ private final AccessibilityManagerService mAms;
+ private final WindowManagerInternal mWindowManager;
+ private final ValueAnimator mTransformationAnimator;
+
+ public MagnificationController(Context context, AccessibilityManagerService ams) {
+ mAms = ams;
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+ final Property<MagnificationController, MagnificationSpec> property =
+ Property.of(MagnificationController.class, MagnificationSpec.class,
+ PROPERTY_NAME_MAGNIFICATION_SPEC);
+ final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+ final long animationDuration = context.getResources().getInteger(
+ R.integer.config_longAnimTime);
+ mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+ mSentMagnificationSpec, mCurrentMagnificationSpec);
+ mTransformationAnimator.setDuration(animationDuration);
+ mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ }
+
+ /**
+ * @return {@code true} if magnification is active, e.g. the scale
+ * is > 1, {@code false} otherwise
+ */
+ public boolean isMagnifying() {
+ return mCurrentMagnificationSpec.scale > 1.0f;
+ }
+
+ /**
+ * Sets the magnified region.
+ *
+ * @param region the region to set
+ * @param updateSpec {@code true} to update the scale and center based on
+ * the region bounds, {@code false} to leave them as-is
+ */
+ public void setMagnifiedRegion(Region region, boolean updateSpec) {
+ mMagnifiedBounds.set(region);
+
+ if (updateSpec) {
+ final Rect magnifiedFrame = mTempRect;
+ region.getBounds(magnifiedFrame);
+ final float scale = mSentMagnificationSpec.scale;
+ final float offsetX = mSentMagnificationSpec.offsetX;
+ final float offsetY = mSentMagnificationSpec.offsetY;
+ final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
+ final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
+ setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
+ } else {
+ mAms.onMagnificationStateChanged();
+ }
+ }
+
+ /**
+ * Returns whether the magnified region contains the specified
+ * screen-relative coordinates.
+ *
+ * @param x the screen-relative X coordinate to check
+ * @param y the screen-relative Y coordinate to check
+ * @return {@code true} if the coordinate is contained within the
+ * magnified region, or {@code false} otherwise
+ */
+ public boolean magnifiedRegionContains(float x, float y) {
+ return mMagnifiedBounds.contains((int) x, (int) y);
+ }
+
+ /**
+ * Populates the specified rect with the bounds of the magnified
+ * region.
+ *
+ * @param outBounds rect to populate with the bounds of the magnified
+ * region
+ */
+ public void getMagnifiedBounds(Rect outBounds) {
+ mMagnifiedBounds.getBounds(outBounds);
+ }
+
+ /**
+ * Returns the magnification scale. If an animation is in progress,
+ * this reflects the end state of the animation.
+ *
+ * @return the scale
+ */
+ public float getScale() {
+ return mCurrentMagnificationSpec.scale;
+ }
+
+ /**
+ * Returns the X offset of the magnification viewport. If an animation
+ * is in progress, this reflects the end state of the animation.
+ *
+ * @return the X offset
+ */
+ public float getOffsetX() {
+ return mCurrentMagnificationSpec.offsetX;
+ }
+
+ /**
+ * Returns the Y offset of the magnification viewport. If an animation
+ * is in progress, this reflects the end state of the animation.
+ *
+ * @return the Y offset
+ */
+ public float getOffsetY() {
+ return mCurrentMagnificationSpec.offsetY;
+ }
+
+ /**
+ * Returns the scale currently used by the window manager. If an
+ * animation is in progress, this reflects the current state of the
+ * animation.
+ *
+ * @return the scale currently used by the window manager
+ */
+ public float getSentScale() {
+ return mSentMagnificationSpec.scale;
+ }
+
+ /**
+ * Returns the X offset currently used by the window manager. If an
+ * animation is in progress, this reflects the current state of the
+ * animation.
+ *
+ * @return the X offset currently used by the window manager
+ */
+ public float getSentOffsetX() {
+ return mSentMagnificationSpec.offsetX;
+ }
+
+ /**
+ * Returns the Y offset currently used by the window manager. If an
+ * animation is in progress, this reflects the current state of the
+ * animation.
+ *
+ * @return the Y offset currently used by the window manager
+ */
+ public float getSentOffsetY() {
+ return mSentMagnificationSpec.offsetY;
+ }
+
+ /**
+ * Resets the magnification scale and center, optionally animating the
+ * transition.
+ *
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ */
+ public void reset(boolean animate) {
+ if (mTransformationAnimator.isRunning()) {
+ mTransformationAnimator.cancel();
+ }
+ mCurrentMagnificationSpec.clear();
+ if (animate) {
+ animateMagnificationSpec(mSentMagnificationSpec,
+ mCurrentMagnificationSpec);
+ } else {
+ setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+ final Rect bounds = mTempRect;
+ bounds.setEmpty();
+ mAms.onMagnificationStateChanged();
+ }
+
+ /**
+ * Scales the magnified region around the specified pivot point,
+ * optionally animating the transition. If animation is disabled, the
+ * transition is immediate.
+ *
+ * @param scale the target scale, must be >= 1
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ */
+ public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
+ final Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
+ final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
+ final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
+ final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+ }
+
+ /**
+ * Sets the center of the magnified region, optionally animating the
+ * transition. If animation is disabled, the transition is immediate.
+ *
+ * @param centerX the screen-relative X coordinate around which to
+ * center
+ * @param centerY the screen-relative Y coordinate around which to
+ * center
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ */
+ public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
+ setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
+ animate);
+ }
+
+ /**
+ * Sets the scale and center of the magnified region, optionally
+ * animating the transition. If animation is disabled, the transition
+ * is immediate.
+ *
+ * @param scale the target scale, must be >= 1
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ */
+ public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
+ boolean animate) {
+ if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
+ && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
+ && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
+ return;
+ }
+ if (mTransformationAnimator.isRunning()) {
+ mTransformationAnimator.cancel();
+ }
+ if (DEBUG_MAGNIFICATION_CONTROLLER) {
+ Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
+ }
+ updateMagnificationSpec(scale, centerX, centerY);
+ if (animate) {
+ animateMagnificationSpec(mSentMagnificationSpec,
+ mCurrentMagnificationSpec);
+ } else {
+ setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+ mAms.onMagnificationStateChanged();
+ }
+
+ /**
+ * Offsets the center of the magnified region.
+ *
+ * @param offsetX the amount in pixels to offset the X center
+ * @param offsetY the amount in pixels to offset the Y center
+ */
+ public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+ getMinOffsetX()), 0);
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+ getMinOffsetY()), 0);
+ setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+
+ private void updateMagnificationSpec(float scale, float magnifiedCenterX,
+ float magnifiedCenterY) {
+ final Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ mCurrentMagnificationSpec.scale = scale;
+ final int viewportWidth = magnifiedFrame.width();
+ final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
+ mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+ getMinOffsetX()), 0);
+ final int viewportHeight = magnifiedFrame.height();
+ final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
+ mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+ getMinOffsetY()), 0);
+ }
+
+ private float getMinOffsetX() {
+ final Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final float viewportWidth = magnifiedFrame.width();
+ return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
+ }
+
+ private float getMinOffsetY() {
+ final Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final float viewportHeight = magnifiedFrame.height();
+ return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
+ }
+
+ private void animateMagnificationSpec(MagnificationSpec fromSpec,
+ MagnificationSpec toSpec) {
+ mTransformationAnimator.setObjectValues(fromSpec, toSpec);
+ mTransformationAnimator.start();
+ }
+
+ private void setMagnificationSpec(MagnificationSpec spec) {
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.i(LOG_TAG, "Sending: " + spec);
+ }
+ mSentMagnificationSpec.scale = spec.scale;
+ mSentMagnificationSpec.offsetX = spec.offsetX;
+ mSentMagnificationSpec.offsetY = spec.offsetY;
+ mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
+ }
+
+ private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
+ private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+
+ @Override
+ public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+ MagnificationSpec toSpec) {
+ final MagnificationSpec result = mTempTransformationSpec;
+ result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+ result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+ result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+ return result;
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
index 8845bc0..8feb167 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -16,9 +16,6 @@
package com.android.server.accessibility;
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
-import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,14 +28,12 @@
import android.os.Message;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.Property;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.InputDevice;
import android.view.KeyEvent;
-import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
@@ -48,7 +43,6 @@
import android.view.ViewConfiguration;
import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
@@ -101,10 +95,8 @@
private static final boolean DEBUG_STATE_TRANSITIONS = false;
private static final boolean DEBUG_DETECTING = false;
- private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private static final boolean DEBUG_PANNING = false;
private static final boolean DEBUG_SCALING = false;
- private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
private static final int STATE_DELEGATING = 1;
private static final int STATE_DETECTING = 2;
@@ -134,8 +126,6 @@
private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
private final StateViewportDraggingHandler mStateViewportDraggingHandler;
- private final AccessibilityManagerService mAms;
-
private final int mUserId;
private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
@@ -143,10 +133,6 @@
private final int mTapDistanceSlop;
private final int mMultiTapDistanceSlop;
- private final long mLongAnimationDuration;
-
- private final Region mMagnifiedBounds = new Region();
-
private EventStreamTransformation mNext;
private int mCurrentState;
@@ -194,13 +180,10 @@
mContext = context;
mUserId = userId;
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
- mAms = service;
mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+ mContext.getResources().getInteger(
com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
- mLongAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
@@ -209,7 +192,7 @@
mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
context);
- mMagnificationController = new MagnificationController(mLongAnimationDuration);
+ mMagnificationController = service.getMagnificationController();
mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
mWindowManager.setMagnificationCallbacks(this);
@@ -230,19 +213,9 @@
// If there was a rotation we have to update the center of the magnified
// region since the old offset X/Y may be out of its acceptable range for
// the new display width and height.
- if (mUpdateMagnificationSpecOnNextBoundsChange) {
- mUpdateMagnificationSpecOnNextBoundsChange = false;
- MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
- Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float scale = spec.scale;
- final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale;
- final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale;
- mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX,
- centerY, false);
- }
- mMagnifiedBounds.set(bounds);
- mAms.onMagnificationStateChanged();
+ mMagnificationController.setMagnifiedRegion(
+ bounds, mUpdateMagnificationSpecOnNextBoundsChange);
+ mUpdateMagnificationSpecOnNextBoundsChange = false;
}
@Override
@@ -257,7 +230,7 @@
private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
+ mMagnificationController.getMagnifiedBounds(magnifiedFrame);
if (!magnifiedFrame.intersects(left, top, right, bottom)) {
return;
}
@@ -314,10 +287,12 @@
}
private void getMagnifiedFrameInContentCoords(Rect rect) {
- MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
- mMagnifiedBounds.getBounds(rect);
- rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
- rect.scale(1.0f / spec.scale);
+ final float scale = mMagnificationController.getSentScale();
+ final float offsetX = mMagnificationController.getSentOffsetX();
+ final float offsetY = mMagnificationController.getSentOffsetY();
+ mMagnificationController.getMagnifiedBounds(rect);
+ rect.offset((int) -offsetX, (int) -offsetY);
+ rect.scale(1.0f / scale);
}
private void resetMagnificationIfNeeded() {
@@ -421,7 +396,7 @@
final float eventX = event.getX();
final float eventY = event.getY();
if (mMagnificationController.isMagnifying()
- && mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
+ && mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
final float scale = mMagnificationController.getScale();
final float scaledOffsetX = mMagnificationController.getOffsetX();
final float scaledOffsetY = mMagnificationController.getOffsetY();
@@ -623,7 +598,7 @@
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
+ if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
if (mLastMoveOutsideMagnifiedRegion) {
mLastMoveOutsideMagnifiedRegion = false;
mMagnificationController.setMagnifiedRegionCenter(eventX,
@@ -696,8 +671,8 @@
switch (action) {
case MotionEvent.ACTION_DOWN: {
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- if (!mMagnifiedBounds.contains((int) event.getX(),
- (int) event.getY())) {
+ if (!mMagnificationController.magnifiedRegionContains(
+ event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
return;
}
@@ -738,7 +713,8 @@
return;
}
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
- if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) {
+ if (!mMagnificationController.magnifiedRegionContains(
+ event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
return;
}
@@ -947,184 +923,6 @@
}
}
- private final class MagnificationController {
-
- private static final String PROPERTY_NAME_MAGNIFICATION_SPEC =
- "magnificationSpec";
-
- private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
-
- private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
-
- private final Rect mTempRect = new Rect();
-
- private final ValueAnimator mTransformationAnimator;
-
- public MagnificationController(long animationDuration) {
- Property<MagnificationController, MagnificationSpec> property =
- Property.of(MagnificationController.class, MagnificationSpec.class,
- PROPERTY_NAME_MAGNIFICATION_SPEC);
- TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
- private final MagnificationSpec mTempTransformationSpec =
- MagnificationSpec.obtain();
- @Override
- public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- MagnificationSpec result = mTempTransformationSpec;
- result.scale = fromSpec.scale
- + (toSpec.scale - fromSpec.scale) * fraction;
- result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX)
- * fraction;
- result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY)
- * fraction;
- return result;
- }
- };
- mTransformationAnimator = ObjectAnimator.ofObject(this, property,
- evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
- mTransformationAnimator.setDuration((long) (animationDuration));
- mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
- }
-
- public boolean isMagnifying() {
- return mCurrentMagnificationSpec.scale > 1.0f;
- }
-
- public void reset(boolean animate) {
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
- }
- mCurrentMagnificationSpec.clear();
- if (animate) {
- animateMangificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
- Rect bounds = mTempRect;
- bounds.setEmpty();
- mAms.onMagnificationStateChanged();
- }
-
- public float getScale() {
- return mCurrentMagnificationSpec.scale;
- }
-
- public float getOffsetX() {
- return mCurrentMagnificationSpec.offsetX;
- }
-
- public float getOffsetY() {
- return mCurrentMagnificationSpec.offsetY;
- }
-
- public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
- Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
- final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
- final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
- final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
- }
-
- public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
- setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
- animate);
- }
-
- public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
-
- public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
- boolean animate) {
- if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetX,
- centerX) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetY,
- centerY) == 0) {
- return;
- }
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
- }
- if (DEBUG_MAGNIFICATION_CONTROLLER) {
- Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX
- + " offsetY: " + centerY);
- }
- updateMagnificationSpec(scale, centerX, centerY);
- if (animate) {
- animateMangificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
- mAms.onMagnificationStateChanged();
- }
-
- public void updateMagnificationSpec(float scale, float magnifiedCenterX,
- float magnifiedCenterY) {
- Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- mCurrentMagnificationSpec.scale = scale;
- final int viewportWidth = magnifiedFrame.width();
- final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final int viewportHeight = magnifiedFrame.height();
- final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
- }
-
- private float getMinOffsetX() {
- Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportWidth = magnifiedFrame.width();
- return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
- }
-
- private float getMinOffsetY() {
- Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportHeight = magnifiedFrame.height();
- return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
- }
-
- private void animateMangificationSpec(MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- mTransformationAnimator.setObjectValues(fromSpec, toSpec);
- mTransformationAnimator.start();
- }
-
- public MagnificationSpec getMagnificationSpec() {
- return mSentMagnificationSpec;
- }
-
- public void setMagnificationSpec(MagnificationSpec spec) {
- if (DEBUG_SET_MAGNIFICATION_SPEC) {
- Slog.i(LOG_TAG, "Sending: " + spec);
- }
- mSentMagnificationSpec.scale = spec.scale;
- mSentMagnificationSpec.offsetX = spec.offsetX;
- mSentMagnificationSpec.offsetY = spec.offsetY;
- mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
- }
- }
-
private final class ScreenStateObserver extends BroadcastReceiver {
private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 61fe62f..48e96aa 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -123,6 +123,7 @@
private int mLastBatteryTemperature;
private boolean mLastBatteryLevelCritical;
private int mLastMaxChargingCurrent;
+ private int mLastMaxChargingVoltage;
private int mInvalidCharger;
private int mLastInvalidCharger;
@@ -339,6 +340,7 @@
+ ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
+ ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
+ ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
+ + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
+ ", batteryStatus=" + mBatteryProps.batteryStatus
+ ", batteryHealth=" + mBatteryProps.batteryHealth
+ ", batteryPresent=" + mBatteryProps.batteryPresent
@@ -370,6 +372,7 @@
mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
+ mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
mInvalidCharger != mLastInvalidCharger)) {
if (mPlugType != mLastPlugType) {
@@ -497,6 +500,7 @@
mLastBatteryVoltage = mBatteryProps.batteryVoltage;
mLastBatteryTemperature = mBatteryProps.batteryTemperature;
mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
+ mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
}
@@ -522,7 +526,7 @@
intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel +
", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
@@ -535,7 +539,8 @@
", USB powered:" + mBatteryProps.chargerUsbOnline +
", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
", icon:" + icon + ", invalid charger:" + mInvalidCharger +
- ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent);
+ ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent +
+ ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage);
}
mHandler.post(new Runnable() {
@@ -766,6 +771,7 @@
pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline);
pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline);
pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent);
+ pw.println(" Max charging voltage: " + mBatteryProps.maxChargingVoltage);
pw.println(" status: " + mBatteryProps.batteryStatus);
pw.println(" health: " + mBatteryProps.batteryHealth);
pw.println(" present: " + mBatteryProps.batteryPresent);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 85187c7..a7879c6 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -562,6 +562,8 @@
private static final int H_VOLUME_BROADCAST = 6;
private static final int H_INTERNAL_BROADCAST = 7;
private static final int H_VOLUME_UNMOUNT = 8;
+ private static final int H_PARTITION_FORGET = 9;
+ private static final int H_RESET = 10;
class MountServiceHandler extends Handler {
public MountServiceHandler(Looper looper) {
@@ -669,6 +671,16 @@
final Intent intent = (Intent) msg.obj;
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ break;
+ }
+ case H_PARTITION_FORGET: {
+ final String partGuid = (String) msg.obj;
+ forgetPartition(partGuid);
+ break;
+ }
+ case H_RESET: {
+ resetIfReadyAndConnected();
+ break;
}
}
}
@@ -753,9 +765,7 @@
}
private void handleSystemReady() {
- synchronized (mLock) {
- resetIfReadyAndConnectedLocked();
- }
+ resetIfReadyAndConnected();
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
@@ -793,7 +803,7 @@
}
}
- private void addInternalVolume() {
+ private void addInternalVolumeLocked() {
// Create a stub volume that represents internal storage
final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
VolumeInfo.TYPE_PRIVATE, null, null);
@@ -802,18 +812,22 @@
mVolumes.put(internal.id, internal);
}
- private void resetIfReadyAndConnectedLocked() {
+ private void resetIfReadyAndConnected() {
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ ", mDaemonConnected=" + mDaemonConnected);
if (mSystemReady && mDaemonConnected) {
- final UserManager um = UserManager.get(mContext);
- final List<UserInfo> users = um.getUsers();
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
killMediaProvider(users);
- mDisks.clear();
- mVolumes.clear();
+ final int[] startedUsers;
+ synchronized (mLock) {
+ startedUsers = mStartedUsers;
- addInternalVolume();
+ mDisks.clear();
+ mVolumes.clear();
+
+ addInternalVolumeLocked();
+ }
try {
mConnector.execute("volume", "reset");
@@ -822,7 +836,7 @@
for (UserInfo user : users) {
mConnector.execute("volume", "user_added", user.id, user.serialNumber);
}
- for (int userId : mStartedUsers) {
+ for (int userId : startedUsers) {
mConnector.execute("volume", "user_started", userId);
}
} catch (NativeDaemonConnectorException e) {
@@ -898,9 +912,7 @@
}
private void handleDaemonConnected() {
- synchronized (mLock) {
- resetIfReadyAndConnectedLocked();
- }
+ resetIfReadyAndConnected();
/*
* Now that we've done our initialization, release
@@ -1428,6 +1440,7 @@
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
null);
mConnector.setDebug(true);
+ mConnector.setWarnIfHeld(mLock);
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
@@ -1445,7 +1458,9 @@
userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
- addInternalVolume();
+ synchronized (mLock) {
+ addInternalVolumeLocked();
+ }
// Add ourself to the Watchdog monitors if enabled.
if (WATCHDOG_ENABLE) {
@@ -1791,10 +1806,11 @@
waitForReady();
Preconditions.checkNotNull(fsUuid);
+
synchronized (mLock) {
final VolumeRecord rec = mRecords.remove(fsUuid);
if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
- forgetPartition(rec.partGuid);
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
@@ -1802,7 +1818,7 @@
// reset vold so we bind into new volume into place.
if (Objects.equals(mPrimaryStorageUuid, fsUuid)) {
mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
writeSettingsLocked();
@@ -1819,7 +1835,7 @@
final String fsUuid = mRecords.keyAt(i);
final VolumeRecord rec = mRecords.valueAt(i);
if (!TextUtils.isEmpty(rec.partGuid)) {
- forgetPartition(rec.partGuid);
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
}
@@ -1830,7 +1846,7 @@
}
writeSettingsLocked();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
}
@@ -1878,7 +1894,7 @@
}
writeSettingsLocked();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
}
@@ -1915,7 +1931,7 @@
Slog.d(TAG, "Skipping move to/from primary physical");
onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
} else {
final VolumeInfo from = findStorageForUuid(mPrimaryStorageUuid);
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 519a2a3..e6b6074 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -28,6 +28,7 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.google.android.collect.Lists;
import java.io.FileDescriptor;
@@ -57,6 +58,7 @@
private LocalLog mLocalLog;
private volatile boolean mDebug = false;
+ private volatile Object mWarnIfHeld;
private final ResponseQueue mResponseQueue;
@@ -107,6 +109,15 @@
mDebug = debug;
}
+ /**
+ * Yell loudly if someone tries making future {@link #execute(Command)}
+ * calls while holding a lock on the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ Preconditions.checkState(mWarnIfHeld == null);
+ mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+ }
+
@Override
public void run() {
mCallbackHandler = new Handler(mLooper, this);
@@ -394,6 +405,11 @@
*/
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
+ if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+ }
+
final long startTime = SystemClock.elapsedRealtime();
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index a0d305c..3f0664d 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -30,6 +30,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.util.NtpTrustedTime;
@@ -75,6 +76,7 @@
private SettingsObserver mSettingsObserver;
// The last time that we successfully fetched the NTP time.
private long mLastNtpFetchTime = NOT_SET;
+ private final PowerManager.WakeLock mWakeLock;
// Normal polling frequency
private final long mPollingIntervalMs;
@@ -104,6 +106,9 @@
com.android.internal.R.integer.config_ntpRetry);
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
+
+ mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
/** Initialize the receivers and initiate the first NTP request */
@@ -148,7 +153,15 @@
private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother.
if (!isAutomaticTimeRequested()) return;
+ mWakeLock.acquire();
+ try {
+ onPollNetworkTimeUnderWakeLock(event);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ private void onPollNetworkTimeUnderWakeLock(int event) {
final long refTime = SystemClock.elapsedRealtime();
// If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df6b1d6..befaaef 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -337,7 +337,7 @@
ServiceRecord r = res.record;
- if (!mAm.getUserManagerLocked().exists(r.userId)) {
+ if (!mAm.mUserController.exists(r.userId)) {
Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
return null;
}
@@ -1030,8 +1030,8 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
- userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
- false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
+ userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
ServiceMap smap = getServiceMap(userId);
final ComponentName comp = service.getComponent();
@@ -2333,7 +2333,8 @@
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr);
- } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
+ } else if (!allowRestart
+ || !mAm.mUserController.isUserRunningLocked(sr.userId, false)) {
bringDownServiceLocked(sr);
} else {
boolean canceled = scheduleServiceRestartLocked(sr, true);
@@ -2446,7 +2447,7 @@
if (ActivityManager.checkUidPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
uid) == PackageManager.PERMISSION_GRANTED) {
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
for (int ui=0; ui<users.length && res.size() < maxNum; ui++) {
ArrayMap<ComponentName, ServiceRecord> alls = getServices(users[ui]);
for (int i=0; i<alls.size() && res.size() < maxNum; i++) {
@@ -2580,7 +2581,7 @@
pw.print(mLastAnrDump);
pw.println();
}
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
for (int user : users) {
ServiceMap smap = getServiceMap(user);
boolean printed = false;
@@ -2817,7 +2818,7 @@
ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
synchronized (mAm) {
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
if ("all".equals(name)) {
for (int user : users) {
ServiceMap smap = mServiceMap.get(user);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5aab804..546db3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19,12 +19,11 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -47,6 +46,7 @@
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
+import android.app.ActivityManager.StackId;
import android.app.AppOpsManager;
import android.app.ApplicationThreadNative;
import android.app.BroadcastOptions;
@@ -207,7 +207,6 @@
import android.os.IBinder;
import android.os.IPermissionController;
import android.os.IProcessInfoService;
-import android.os.IUserManager;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -375,6 +374,13 @@
// we will consider it to be doing interaction for usage stats.
static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
+ // Maximum amount of time we will allow to elapse before re-reporting usage stats
+ // interaction with foreground processes.
+ static final long USAGE_STATS_INTERACTION_INTERVAL = 24*60*60*1000L;
+
+ // Maximum number of users we allow to be running at a time.
+ static final int MAX_RUNNING_USERS = 3;
+
// How long to wait in getAssistContextExtras for the activity and foreground services
// to respond with the result.
static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -1271,8 +1277,6 @@
final ActivityThread mSystemThread;
- private UserManagerService mUserManager;
-
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
@@ -1400,7 +1404,7 @@
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
&& proc.pid != MY_PID);
- for (int userId : mUserController.mCurrentProfileIds) {
+ for (int userId : mUserController.getCurrentProfileIdsLocked()) {
isBackground &= (proc.userId != userId);
}
if (isBackground && !showBackground) {
@@ -1565,7 +1569,7 @@
break;
}
case START_USER_SWITCH_MSG: {
- showUserSwitchDialog(msg.arg1, (String) msg.obj);
+ mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
break;
}
case DISMISS_DIALOG_MSG: {
@@ -3591,8 +3595,7 @@
void enforceShellRestriction(String restriction, int userHandle) {
if (Binder.getCallingUid() == Process.SHELL_UID) {
- if (userHandle < 0
- || mUserManager.hasUserRestriction(restriction, userHandle)) {
+ if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
throw new SecurityException("Shell does not have permission to access user "
+ userHandle);
}
@@ -3854,8 +3857,8 @@
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivity", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -3945,8 +3948,8 @@
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityAndWait");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
@@ -3960,8 +3963,8 @@
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, Configuration config, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityWithConfig");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -4018,8 +4021,8 @@
if (session == null || interactor == null) {
throw new NullPointerException("null session or interactor");
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- false, ALLOW_FULL_ONLY, "startVoiceActivity", null);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ALLOW_FULL_ONLY, "startVoiceActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null,
@@ -4089,8 +4092,8 @@
if (debug) {
Slog.v(TAG, "Next matching activity: found current " + r.packageName
+ "/" + r.info.name);
- Slog.v(TAG, "Next matching activity: next is " + aInfo.packageName
- + "/" + aInfo.name);
+ Slog.v(TAG, "Next matching activity: next is " + ((aInfo == null)
+ ? "null" : aInfo.packageName + "/" + aInfo.name));
}
break;
}
@@ -4207,8 +4210,8 @@
String resultWho, int requestCode, int startFlags, Bundle options, int userId,
IActivityContainer container, TaskRecord inTask) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent,
@@ -4222,8 +4225,8 @@
Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options,
int userId) {
enforceNotIsolatedCaller("startActivities");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivity", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
resolvedTypes, resultTo, options, userId);
@@ -4234,8 +4237,8 @@
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
resultTo, options, userId);
@@ -5136,8 +5139,8 @@
}
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
- userId = handleIncomingUser(pid, uid,
- userId, false, ALLOW_FULL_ONLY, "clearApplicationUserData", null);
+ userId = mUserController.handleIncomingUser(pid, uid, userId, false,
+ ALLOW_FULL_ONLY, "clearApplicationUserData", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -5216,7 +5219,7 @@
throw new SecurityException(msg);
}
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
long callingId = Binder.clearCallingIdentity();
try {
@@ -5299,14 +5302,14 @@
throw new SecurityException(msg);
}
final int callingPid = Binder.getCallingPid();
- userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
int[] users = userId == UserHandle.USER_ALL
- ? getUsersLocked() : new int[] { userId };
+ ? mUserController.getUsers() : new int[] { userId };
for (int user : users) {
int pkgUid = -1;
try {
@@ -5324,7 +5327,7 @@
Slog.w(TAG, "Failed trying to unstop package "
+ packageName + ": " + e);
}
- if (isUserRunningLocked(user, false)) {
+ if (mUserController.isUserRunningLocked(user, false)) {
forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
}
}
@@ -6662,7 +6665,7 @@
synchronized(this) {
int callingUid = Binder.getCallingUid();
int origUserId = userId;
- userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
type == ActivityManager.INTENT_SENDER_BROADCAST,
ALLOW_NON_FULL, "getIntentSender", null);
if (origUserId == UserHandle.USER_CURRENT) {
@@ -7910,8 +7913,9 @@
@Override
public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri,
final int modeFlags, int sourceUserId, int targetUserId) {
- targetUserId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- targetUserId, false, ALLOW_FULL_ONLY, "grantUriPermissionFromOwner", null);
+ targetUserId = mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), targetUserId, false, ALLOW_FULL_ONLY,
+ "grantUriPermissionFromOwner", null);
synchronized(this) {
UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
@@ -8362,6 +8366,9 @@
rti.affiliatedTaskId = tr.mAffiliatedTaskId;
rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
rti.numActivities = 0;
+ if (tr.mBounds != null) {
+ rti.bounds = new Rect(tr.mBounds);
+ }
ActivityRecord base = null;
ActivityRecord top = null;
@@ -8415,7 +8422,7 @@
@Override
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
final int callingUid = Binder.getCallingUid();
- userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false, ALLOW_FULL_ONLY, "getRecentTasks", null);
final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
@@ -8433,7 +8440,7 @@
final Set<Integer> includedUsers;
if (includeProfiles) {
- includedUsers = getProfileIdsLocked(userId);
+ includedUsers = mUserController.getProfileIds(userId);
} else {
includedUsers = new HashSet<>();
}
@@ -8651,12 +8658,9 @@
// - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
// that task to freeform
// - otherwise the task is not moved
- // Note it's not allowed to resize a home, docked, or pinned stack task.
int stackId = task.stack.mStackId;
- if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID
- || stackId == PINNED_STACK_ID) {
- throw new IllegalArgumentException("trying to resizeTask on a "
- + "home or docked task");
+ if (!StackId.isTaskResizeAllowed(stackId)) {
+ throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
}
if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
stackId = FULLSCREEN_WORKSPACE_STACK_ID;
@@ -9458,16 +9462,15 @@
boolean checkedGrants = false;
if (checkUser) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
- int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
+ int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
return null;
}
checkedGrants = true;
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- false, ALLOW_NON_FULL,
- "checkContentProviderPermissionLocked " + cpi.authority, null);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ALLOW_NON_FULL, "checkContentProviderPermissionLocked " + cpi.authority, null);
if (userId != tmpTargetUserId) {
// When we actually went to determine the final targer user ID, this ended
// up different than our initial check for the authority. This is because
@@ -9806,7 +9809,7 @@
// Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
- if (!isUserRunningLocked(userId, false)) {
+ if (!mUserController.isUserRunningLocked(userId, false)) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
@@ -9992,8 +9995,8 @@
String name, int userId, IBinder token) {
enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
"Do not have permission in call getContentProviderExternal()");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "getContentProvider", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "getContentProvider", null);
return getContentProviderExternalUnchecked(name, token, userId);
}
@@ -10305,7 +10308,7 @@
long ident = 0;
boolean clearedIdentity = false;
synchronized (this) {
- userId = unsafeConvertIncomingUserLocked(userId);
+ userId = mUserController.unsafeConvertIncomingUserLocked(userId);
}
if (canClearIdentity(callingPid, callingUid, userId)) {
clearedIdentity = true;
@@ -10975,7 +10978,7 @@
public boolean isAssistDataAllowedOnCurrentActivity() {
int userId;
synchronized (this) {
- userId = mUserController.mCurrentUserId;
+ userId = mUserController.getCurrentUserIdLocked();
ActivityRecord activity = getFocusedStack().topActivity();
if (activity == null) {
return false;
@@ -11890,7 +11893,7 @@
}
// TODO: can we still do this with per user encryption?
- final int[] users = getUsersLocked();
+ final int[] users = mUserController.getUsers();
if (users.length <= 0) {
return false;
}
@@ -11920,8 +11923,7 @@
mUserController.updateCurrentProfileIdsLocked();
mRecentTasks.clear();
- mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
- getUserManagerLocked().getUserIds()));
+ mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds()));
mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
mTaskPersister.startPersisting();
@@ -12026,7 +12028,7 @@
loadResourcesOnSystemReady();
final int currentUserId;
synchronized (this) {
- currentUserId = mUserController.mCurrentUserId;
+ currentUserId = mUserController.getCurrentUserIdLocked();
readGrantedUriPermissionsLocked();
}
@@ -12110,7 +12112,7 @@
Binder.restoreCallingIdentity(ident);
}
mStackSupervisor.resumeTopActivitiesLocked();
- sendUserSwitchBroadcastsLocked(-1, currentUserId);
+ mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
}
}
@@ -12310,7 +12312,7 @@
// launching the report UI under a different user.
app.errorReportReceiver = null;
- for (int userId : mUserController.mCurrentProfileIds) {
+ for (int userId : mUserController.getCurrentProfileIdsLocked()) {
if (app.userId == userId) {
app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
mContext, app.info.packageName, app.info.flags);
@@ -16031,97 +16033,10 @@
@Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
- return handleIncomingUser(callingPid, callingUid, userId, allowAll,
+ return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
}
- int unsafeConvertIncomingUserLocked(int userId) {
- return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
- ? mUserController.mCurrentUserId : userId;
- }
-
- int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
- int allowMode, String name, String callerPackage) {
- final int callingUserId = UserHandle.getUserId(callingUid);
- if (callingUserId == userId) {
- return userId;
- }
-
- // Note that we may be accessing mCurrentUserId outside of a lock...
- // shouldn't be a big deal, if this is being called outside
- // of a locked context there is intrinsically a race with
- // the value the caller will receive and someone else changing it.
- // We assume that USER_CURRENT_OR_SELF will use the current user; later
- // we will switch to the calling user if access to the current user fails.
- int targetUserId = unsafeConvertIncomingUserLocked(userId);
-
- if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- final boolean allow;
- if (checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
- // If the caller has this permission, they always pass go. And collect $200.
- allow = true;
- } else if (allowMode == ALLOW_FULL_ONLY) {
- // We require full access, sucks to be you.
- allow = false;
- } else if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
- callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
- // If the caller does not have either permission, they are always doomed.
- allow = false;
- } else if (allowMode == ALLOW_NON_FULL) {
- // We are blanket allowing non-full access, you lucky caller!
- allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
- // We may or may not allow this depending on whether the two users are
- // in the same profile.
- allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
- } else {
- throw new IllegalArgumentException("Unknown mode: " + allowMode);
- }
- if (!allow) {
- if (userId == UserHandle.USER_CURRENT_OR_SELF) {
- // In this case, they would like to just execute as their
- // owner user instead of failing.
- targetUserId = callingUserId;
- } else {
- StringBuilder builder = new StringBuilder(128);
- builder.append("Permission Denial: ");
- builder.append(name);
- if (callerPackage != null) {
- builder.append(" from ");
- builder.append(callerPackage);
- }
- builder.append(" asks to run as user ");
- builder.append(userId);
- builder.append(" but is calling from user ");
- builder.append(UserHandle.getUserId(callingUid));
- builder.append("; this requires ");
- builder.append(INTERACT_ACROSS_USERS_FULL);
- if (allowMode != ALLOW_FULL_ONLY) {
- builder.append(" or ");
- builder.append(INTERACT_ACROSS_USERS);
- }
- String msg = builder.toString();
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
- }
- if (!allowAll && targetUserId < 0) {
- throw new IllegalArgumentException(
- "Call does not support special user #" + targetUserId);
- }
- // Check shell permission
- if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
- targetUserId)) {
- throw new SecurityException("Shell does not have permission to access user "
- + targetUserId + "\n " + Debug.getCallers(3));
- }
- }
- return targetUserId;
- }
-
boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
String className, int flags) {
boolean result = false;
@@ -16434,8 +16349,8 @@
callingPid = Binder.getCallingPid();
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
Iterator<String> actions = filter.actionsIterator();
if (actions == null) {
@@ -16625,8 +16540,8 @@
for (int user : users) {
// Skip users that have Shell restrictions
if (callingUid == Process.SHELL_UID
- && getUserManagerLocked().hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+ && mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
continue;
}
List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
@@ -16715,14 +16630,14 @@
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- true, ALLOW_NON_FULL, "broadcast", callerPackage);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_NON_FULL, "broadcast", callerPackage);
// Make sure that the user who is receiving this broadcast is running.
// If not, we will just skip it. Make an exception for shutdown broadcasts
// and upgrade steps.
- if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
+ if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, false)) {
if ((callingUid != Process.SYSTEM_UID
|| (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
&& !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -17027,9 +16942,8 @@
if (intent.getComponent() == null) {
if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
// Query one target user at a time, excluding shell-restricted users
- UserManagerService ums = getUserManagerLocked();
for (int i = 0; i < users.length; i++) {
- if (ums.hasUserRestriction(
+ if (mUserController.hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
continue;
}
@@ -17247,7 +17161,7 @@
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
synchronized(this) {
@@ -17331,7 +17245,7 @@
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
int userId, String abiOverride) {
enforceNotIsolatedCaller("startInstrumentation");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
// Refuse possible leaked file descriptors
if (arguments != null && arguments.hasFileDescriptors()) {
@@ -17597,10 +17511,11 @@
Binder.restoreCallingIdentity(origId);
}
}
+
void updateUserConfigurationLocked() {
Configuration configuration = new Configuration(mConfiguration);
Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
- mUserController.mCurrentUserId);
+ mUserController.getCurrentUserIdLocked());
updateConfigurationLocked(configuration, null, false);
}
@@ -17649,7 +17564,7 @@
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig,
- mUserController.mCurrentUserId);
+ mUserController.getCurrentUserIdLocked());
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
@@ -18918,7 +18833,8 @@
}
}
- private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now) {
+ private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+ long nowElapsed) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
@@ -19022,7 +18938,7 @@
if (app.setProcState != app.curProcState) {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Proc state change of " + app.processName
- + " to " + app.curProcState);
+ + " to " + app.curProcState);
boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
if (setImportant && !curImportant) {
@@ -19033,14 +18949,14 @@
BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
- app.pid, SystemClock.elapsedRealtime());
+ app.pid, nowElapsed);
}
app.lastCpuTime = app.curCpuTime;
}
// Inform UsageStats of important process state change
// Must be called before updating setProcState
- maybeUpdateUsageStatsLocked(app);
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
app.setProcState = app.curProcState;
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
@@ -19051,6 +18967,11 @@
} else {
app.procStateChanged = true;
}
+ } else if (app.reportedInteraction && (nowElapsed-app.interactionEventTime)
+ > USAGE_STATS_INTERACTION_INTERVAL) {
+ // For apps that sit around for a long time in the interactive state, we need
+ // to report this at least once a day so they don't go idle.
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
}
if (changes != 0) {
@@ -19133,7 +19054,7 @@
String authority) {
if (app == null) return;
if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- UserState userState = mUserController.getStartedUserState(app.userId);
+ UserState userState = mUserController.getStartedUserStateLocked(app.userId);
if (userState == null) return;
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -19145,7 +19066,7 @@
}
}
- private void maybeUpdateUsageStatsLocked(ProcessRecord app) {
+ private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ "] state changes: old = " + app.setProcState + ", new = "
@@ -19162,19 +19083,20 @@
isInteraction = true;
app.fgInteractionTime = 0;
} else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
- final long now = SystemClock.elapsedRealtime();
if (app.fgInteractionTime == 0) {
- app.fgInteractionTime = now;
+ app.fgInteractionTime = nowElapsed;
isInteraction = false;
} else {
- isInteraction = now > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
+ isInteraction = nowElapsed > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
}
} else {
isInteraction = app.curProcState
<= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
app.fgInteractionTime = 0;
}
- if (isInteraction && !app.reportedInteraction) {
+ if (isInteraction && (!app.reportedInteraction
+ || (nowElapsed-app.interactionEventTime) > USAGE_STATS_INTERACTION_INTERVAL)) {
+ app.interactionEventTime = nowElapsed;
String[] packages = app.getPackageList();
if (packages != null) {
for (int i = 0; i < packages.length; i++) {
@@ -19184,6 +19106,9 @@
}
}
app.reportedInteraction = isInteraction;
+ if (!isInteraction) {
+ app.interactionEventTime = 0;
+ }
}
private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
@@ -19206,7 +19131,7 @@
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
- return applyOomAdjLocked(app, doingAll, now);
+ return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -19298,6 +19223,7 @@
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
@@ -19426,7 +19352,7 @@
}
}
- applyOomAdjLocked(app, true, now);
+ applyOomAdjLocked(app, true, now, nowElapsed);
// Count the number of process types.
switch (app.curProcState) {
@@ -19855,7 +19781,7 @@
}
private ProcessRecord findProcessLocked(String process, int userId, String callName) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, callName, null);
ProcessRecord proc = null;
try {
@@ -20016,31 +19942,12 @@
return mUserController.startUser(userId, /* foreground */ false);
}
- /**
- * Start user, if its not already running, and bring it to foreground.
- */
- boolean startUserInForeground(final int userId, Dialog dlg) {
- boolean result = mUserController.startUser(userId, /* foreground */ true);
- dlg.dismiss();
- return result;
- }
-
- private Set<Integer> getProfileIdsLocked(int userId) {
- Set<Integer> userIds = new HashSet<Integer>();
- final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
- userId, false /* enabledOnly */);
- for (UserInfo user : profiles) {
- userIds.add(Integer.valueOf(user.id));
- }
- return userIds;
- }
-
@Override
public boolean switchUser(final int userId) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
String userName;
synchronized (this) {
- UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+ UserInfo userInfo = mUserController.getUserInfo(userId);
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
@@ -20050,68 +19957,13 @@
return false;
}
userName = userInfo.name;
- mUserController.mTargetUserId = userId;
+ mUserController.setTargetUserIdLocked(userId);
}
mUiHandler.removeMessages(START_USER_SWITCH_MSG);
mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
return true;
}
- private void showUserSwitchDialog(int userId, String userName) {
- // The dialog will show and then initiate the user switch by calling startUserInForeground
- Dialog d = new UserSwitchingDialog(this, mContext, userId, userName,
- true /* above system */);
- d.show();
- }
-
- void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
- long ident = Binder.clearCallingIdentity();
- try {
- Intent intent;
- if (oldUserId >= 0) {
- // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
- List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false);
- int count = profiles.size();
- for (int i = 0; i < count; i++) {
- int profileUserId = profiles.get(i).id;
- intent = new Intent(Intent.ACTION_USER_BACKGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
- }
- }
- if (newUserId >= 0) {
- // Send USER_FOREGROUND broadcast to all profiles of the incoming user
- List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false);
- int count = profiles.size();
- for (int i = 0; i < count; i++) {
- int profileUserId = profiles.get(i).id;
- intent = new Intent(Intent.ACTION_USER_FOREGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
- }
- intent = new Intent(Intent.ACTION_USER_SWITCHED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null,
- new String[] {android.Manifest.permission.MANAGE_USERS},
- AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
void scheduleStartProfilesLocked() {
if (!mHandler.hasMessages(START_PROFILES_MSG)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20145,22 +19997,10 @@
throw new SecurityException(msg);
}
synchronized (this) {
- return isUserRunningLocked(userId, orStopped);
+ return mUserController.isUserRunningLocked(userId, orStopped);
}
}
- boolean isUserRunningLocked(int userId, boolean orStopped) {
- UserState state = mUserController.getStartedUserState(userId);
- if (state == null) {
- return false;
- }
- if (orStopped) {
- return true;
- }
- return state.mState != UserState.STATE_STOPPING
- && state.mState != UserState.STATE_SHUTDOWN;
- }
-
@Override
public int[] getRunningUserIds() {
if (checkCallingPermission(INTERACT_ACROSS_USERS)
@@ -20187,19 +20027,6 @@
mUserController.unregisterUserSwitchObserver(observer);
}
- int[] getUsersLocked() {
- UserManagerService ums = getUserManagerLocked();
- return ums != null ? ums.getUserIds() : new int[] { 0 };
- }
-
- UserManagerService getUserManagerLocked() {
- if (mUserManager == null) {
- IBinder b = ServiceManager.getService(Context.USER_SERVICE);
- mUserManager = (UserManagerService)IUserManager.Stub.asInterface(b);
- }
- return mUserManager;
- }
-
private int applyUserId(int uid, int userId) {
return UserHandle.getUid(userId, uid);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b5f424d..ee9aa11 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,14 +16,11 @@
package com.android.server.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -49,6 +46,7 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
@@ -255,9 +253,6 @@
private final LaunchingTaskPositioner mTaskPositioner;
- // If the bounds of task contained in this stack should be persisted across power cycles.
- final boolean mPersistTaskBounds;
-
static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
@@ -367,11 +362,10 @@
mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
mWindowManager = mService.mWindowManager;
mStackId = activityContainer.mStackId;
- mCurrentUser = mService.mUserController.mCurrentUserId;
+ mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
mRecentTasks = recentTasks;
mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
? new LaunchingTaskPositioner() : null;
- mPersistTaskBounds = mStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID;
}
void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
@@ -1365,7 +1359,7 @@
}
}
- if (mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID) {
+ if (StackId.isStaticStack(mStackId)) {
// Visibility of any static stack should have been determined by the conditions above.
return false;
}
@@ -1377,9 +1371,7 @@
continue;
}
- if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID
- || stack.mStackId == HOME_STACK_ID
- || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
// These stacks can't have any dynamic stacks visible behind them.
return false;
}
@@ -1464,29 +1456,35 @@
}
if (r.app == null || r.app.thread == null) {
- // This activity needs to be visible, but isn't even running...
- // get it started and resume if no other stack in this stack is resumed.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Start and freeze screen for " + r);
- if (r != starting) {
- r.startFreezingScreenLocked(r.app, configChanges);
- }
- if (!r.visible || r.mLaunchTaskBehind) {
+ // We need to make sure the app is running if it's the top, or it is
+ // just made visible from invisible.
+ // If the app is already visible, it must have died while it was visible.
+ // In this case, we'll show the dead window but will not restart the app.
+ // Otherwise we could end up thrashing.
+ if (r == top || !r.visible) {
+ // This activity needs to be visible, but isn't even running...
+ // get it started and resume if no other stack in this stack is resumed.
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Starting and making visible: " + r);
- setVisible(r, true);
- }
- if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(
- r, noStackActivityResumed, false);
- if (activityNdx >= activities.size()) {
- // Record may be removed if its process needs to restart.
- activityNdx = activities.size() - 1;
- } else {
- noStackActivityResumed = false;
+ "Start and freeze screen for " + r);
+ if (r != starting) {
+ r.startFreezingScreenLocked(r.app, configChanges);
+ }
+ if (!r.visible || r.mLaunchTaskBehind) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Starting and making visible: " + r);
+ setVisible(r, true);
+ }
+ if (r != starting) {
+ mStackSupervisor.startSpecificActivityLocked(
+ r, noStackActivityResumed, false);
+ if (activityNdx >= activities.size()) {
+ // Record may be removed if its process needs to restart.
+ activityNdx = activities.size() - 1;
+ } else {
+ noStackActivityResumed = false;
+ }
}
}
-
} else if (r.visible) {
// If this activity is already visible, then there is nothing
// else to do here.
@@ -2797,8 +2795,7 @@
ActivityRecord next = topRunningActivityLocked();
final String myReason = reason + " adjustFocus";
if (next != r) {
- if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID
- || mStackId == DOCKED_STACK_ID || mStackId == PINNED_STACK_ID)) {
+ if (next != null && StackId.keepFocusInStackIfPossible(mStackId)) {
// For freeform, docked, and pinned stacks we always keep the focus within the
// stack as long as there is a running activity in the stack that we can adjust
// focus to.
@@ -3740,10 +3737,13 @@
// Don't currently have state for the activity, or
// it is finishing -- always remove it.
remove = true;
- } else if (r.launchCount > 2 &&
- r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
+ } else if (!r.visible && r.launchCount > 2 &&
+ r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
// We have launched this activity too many times since it was
// able to run, so give up and remove it.
+ // (Note if the activity is visible, we don't remove the record.
+ // We leave the dead window on the screen but the process will
+ // not be restarted unless user explicitly tap on it.)
remove = true;
} else {
// The process may be gone, but the activity lives on!
@@ -3773,7 +3773,11 @@
if (DEBUG_APP) Slog.v(TAG_APP,
"Clearing app during removeHistory for activity " + r);
r.app = null;
- r.nowVisible = false;
+ // Set nowVisible to previous visible state. If the app was visible while
+ // it died, we leave the dead window on screen so it's basically visible.
+ // This is needed when user later tap on the dead window, we need to stop
+ // other apps when user transfers focus to the restarted activity.
+ r.nowVisible = r.visible;
if (!r.haveState) {
if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
"App died, clearing saved state of " + r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1cd71a1..f262ec9 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -18,6 +18,15 @@
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.app.ActivityManager.*;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -40,6 +49,7 @@
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -364,13 +374,26 @@
final ActivityRecord sourceRecord;
final int startFlags;
final ActivityStack stack;
+ final ProcessRecord callerApp;
PendingActivityLaunch(ActivityRecord _r, ActivityRecord _sourceRecord,
- int _startFlags, ActivityStack _stack) {
+ int _startFlags, ActivityStack _stack, ProcessRecord _callerApp) {
r = _r;
sourceRecord = _sourceRecord;
startFlags = _startFlags;
stack = _stack;
+ callerApp = _callerApp;
+ }
+
+ void sendErrorResult(String message) {
+ try {
+ if (callerApp.thread != null) {
+ callerApp.thread.scheduleCrash(message);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception scheduling crash of failed "
+ + "activity launcher sourceRecord=" + sourceRecord, e);
+ }
}
}
@@ -554,8 +577,8 @@
* @param id Id of the task we would like returned.
* @param restoreFromRecents If the id was not in the active list, but was found in recents,
* restore the task from recents to the active list.
- * @param stackId The stack to restore the task to (default launch stack will be used
- * if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+ * @param stackId The stack to restore the task to (default launch stack will be used if
+ * stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
*/
TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {
int numDisplays = mActivityDisplays.size();
@@ -1668,8 +1691,8 @@
|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
realCallingPid, realCallingUid, "Activity start")) {
- PendingActivityLaunch pal =
- new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
+ PendingActivityLaunch pal = new PendingActivityLaunch(r,
+ sourceRecord, startFlags, stack, callerApp);
mPendingActivityLaunches.add(pal);
ActivityOptions.abort(options);
return ActivityManager.START_SWITCHES_CANCELED;
@@ -1820,8 +1843,7 @@
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
stack = homeDisplayStacks.get(stackNdx);
- final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
- if (isDynamicStack) {
+ if (!StackId.isStaticStack(stack.mStackId)) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting focused stack=" + stack);
return stack;
@@ -2536,9 +2558,10 @@
try {
startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
- doResume && mPendingActivityLaunches.isEmpty(), null, null);
+ doResume && mPendingActivityLaunches.isEmpty(), null, null);
} catch (Exception e) {
- Slog.w(TAG, "Exception during pending activity launch pal=" + pal, e);
+ Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
+ pal.sendErrorResult(e.getMessage());
}
}
}
@@ -2863,25 +2886,25 @@
return;
}
- int stackId = task.stack.mStackId;
if (task.mResizeable && options != null) {
ActivityOptions opts = new ActivityOptions(options);
if (opts.hasBounds()) {
Rect bounds = opts.getBounds();
task.updateOverrideConfiguration(bounds);
+ final int stackId = task.getLaunchStackId();
+ if (stackId != task.stack.mStackId) {
+ moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+ // moveTaskToStackUncheckedLocked() should already placed the task on top,
+ // still need moveTaskToFrontLocked() below for any transition settings.
+ }
+ // WM resizeTask must be done after the task is moved to the correct stack,
+ // because Task's setBounds() also updates dim layer's bounds, but that has
+ // dependency on the stack.
mWindowManager.resizeTask(task.taskId, bounds, task.mOverrideConfig,
false /*relayout*/, false /*forced*/);
- stackId = task.getLaunchStackId();
}
}
- if (stackId != task.stack.mStackId) {
- moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
-
- // moveTaskToStackUncheckedLocked() should already placed the task on top,
- // still need moveTaskToFrontLocked() below for any transition settings.
- }
-
final ActivityRecord r = task.getTopActivity();
task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
r == null ? null : r.appTimeTracker, reason);
@@ -2899,15 +2922,14 @@
if (activityContainer != null) {
return activityContainer.mStack;
}
- if (!createStaticStackIfNeeded
- || (stackId < FIRST_STATIC_STACK_ID || stackId > LAST_STATIC_STACK_ID)) {
+ if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
return null;
}
return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
}
ArrayList<ActivityStack> getStacks() {
- ArrayList<ActivityStack> allStacks = new ArrayList<ActivityStack>();
+ ArrayList<ActivityStack> allStacks = new ArrayList<>();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
}
@@ -3025,7 +3047,7 @@
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID && getStack(i) != null) {
+ if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
resizeStackLocked(i, null, preserveWindows, true);
}
}
@@ -3046,7 +3068,7 @@
mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID) {
+ if (StackId.isResizeableByDockedStack(i)) {
ActivityStack otherStack = getStack(i);
if (otherStack != null) {
resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
@@ -3190,7 +3212,7 @@
* Restores a recent task to a stack
* @param task The recent task to be restored.
* @param stackId The stack to restore the task to (default launch stack will be used
- * if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+ * if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
* @return true if the task has been restored successfully.
*/
private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
@@ -3275,8 +3297,7 @@
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
- if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (StackId.preserveWindowOnTaskMove(stackId)) {
// We are about to relaunch the activity because its configuration changed due to
// being maximized, i.e. size change. The activity will first remove the old window
// and then add a new one. This call will tell window manager about this, so it can
@@ -4664,7 +4685,7 @@
@Override
public final int startActivity(Intent intent) {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
- final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
@@ -4690,7 +4711,7 @@
throw new IllegalArgumentException("Bad PendingIntent object");
}
- final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 6ed880e..8039072 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
- userId = owner.mUserController.getCurrentUserIdLocked();
+ userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
}
switch (key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 08203c55b..b77eec8 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -114,6 +114,7 @@
boolean killed; // True once we know the process has been killed
boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
+ long interactionEventTime; // The time we sent the last interaction event
long fgInteractionTime; // When we became foreground for interaction purposes
String waitingToKill; // Process is waiting to be killed when in the bg, and reason
IBinder forcingToForeground;// Token that is forcing this process to be foreground
@@ -297,6 +298,10 @@
if (reportedInteraction || fgInteractionTime != 0) {
pw.print(prefix); pw.print("reportedInteraction=");
pw.print(reportedInteraction);
+ if (interactionEventTime != 0) {
+ pw.print(" time=");
+ TimeUtils.formatDuration(interactionEventTime, SystemClock.elapsedRealtime(), pw);
+ }
if (fgInteractionTime != 0) {
pw.print(" fgInteractionTime=");
TimeUtils.formatDuration(fgInteractionTime, SystemClock.elapsedRealtime(), pw);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index edd16ef..862a973 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -108,7 +108,7 @@
final IPackageManager pm = AppGlobals.getPackageManager();
final int[] users = (userId == UserHandle.USER_ALL)
- ? mService.getUsersLocked() : new int[] { userId };
+ ? mService.mUserController.getUsers() : new int[] { userId };
for (int userIdx = 0; userIdx < users.length; userIdx++) {
final int user = users[userIdx];
recentsCount = size() - 1;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 120b40c..5fd213f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,11 +16,9 @@
package com.android.server.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -35,6 +33,7 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskThumbnail;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
@@ -1193,14 +1192,14 @@
mFullscreen = bounds == null;
if (mFullscreen) {
- if (mBounds != null && stack.mPersistTaskBounds) {
+ if (mBounds != null && StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
mBounds = null;
mOverrideConfig = Configuration.EMPTY;
} else {
mBounds = new Rect(bounds);
- if (stack == null || stack.mPersistTaskBounds) {
+ if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
@@ -1244,7 +1243,7 @@
|| stackId == HOME_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
return (mResizeable && stack != null) ? stack.mBounds : null;
- } else if (!stack.mPersistTaskBounds) {
+ } else if (!StackId.persistTaskBounds(stackId)) {
return stack.mBounds;
}
return mLastNonFullscreenBounds;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ff74d83..4085489 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,11 +18,17 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
+import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
@@ -31,8 +37,10 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.Dialog;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
+import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -40,11 +48,15 @@
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.IUserManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -58,7 +70,9 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -76,9 +90,9 @@
private final Handler mHandler;
// Holds the current foreground user's id
- int mCurrentUserId = 0;
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
// Holds the target user's id during a user switch
- int mTargetUserId = UserHandle.USER_NULL;
+ private int mTargetUserId = UserHandle.USER_NULL;
/**
* Which users have been started, so are allowed to run code.
@@ -96,7 +110,7 @@
// If there are multiple profiles for the current user, their ids are here
// Currently only the primary user can have managed profiles
- int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+ private int[] mCurrentProfileIds = new int[] {};
/**
* Mapping from each known user ID to the profile group ID it is associated with.
@@ -106,7 +120,7 @@
/**
* Registered observers of the user switching mechanics.
*/
- final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+ private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
= new RemoteCallbackList<>();
/**
@@ -114,6 +128,8 @@
*/
Object mCurUserSwitchCallback;
+ private volatile UserManagerService mUserManager;
+
UserController(ActivityManagerService service) {
mService = service;
mHandler = mService.mHandler;
@@ -176,8 +192,7 @@
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
- Process.SYSTEM_UID, userId);
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
}
@@ -272,16 +287,14 @@
mService.mSystemServiceManager.stopUser(userId);
mService.broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID,
- android.os.Process.SYSTEM_UID, userId);
+ null, true, false, MY_PID, SYSTEM_UID, userId);
}
};
// Kick things off.
mService.broadcastIntentLocked(null, null, stoppingIntent,
null, stoppingReceiver, 0, null, null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -338,8 +351,7 @@
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -357,7 +369,7 @@
|| oldUss.mState == UserState.STATE_SHUTDOWN) {
continue;
}
- UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+ UserInfo userInfo = getUserInfo(oldUserId);
if (userInfo.isGuest()) {
// This is a user to be stopped.
stopUserLocked(oldUserId, null);
@@ -369,7 +381,7 @@
void startProfilesLocked() {
if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
- List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ List<UserInfo> profiles = getUserManager().getProfiles(
mCurrentUserId, false /* enabledOnly */);
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
@@ -388,8 +400,13 @@
}
}
- private UserManagerService getUserManagerLocked() {
- return mService.getUserManagerLocked();
+ private UserManagerService getUserManager() {
+ UserManagerService userManager = mUserManager;
+ if (userManager == null) {
+ IBinder b = ServiceManager.getService(Context.USER_SERVICE);
+ userManager = mUserManager = (UserManagerService) IUserManager.Stub.asInterface(b);
+ }
+ return userManager;
}
boolean startUser(final int userId, final boolean foreground) {
@@ -416,7 +433,7 @@
mService.mStackSupervisor.setLockTaskModeLocked(null,
ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
- final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+ final UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
@@ -507,8 +524,7 @@
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- userId);
+ null, false, false, MY_PID, SYSTEM_UID, userId);
}
if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
@@ -523,11 +539,10 @@
onUserInitialized(uss, foreground, oldUserId, userId);
}
}, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID,
- Process.SYSTEM_UID, userId);
+ null, true, false, MY_PID, SYSTEM_UID, userId);
uss.initializing = true;
} else {
- getUserManagerLocked().makeInitialized(userInfo.id);
+ getUserManager().makeInitialized(userInfo.id);
}
}
@@ -551,9 +566,8 @@
int sendingUser) throws RemoteException {
}
}, 0, null, null,
- new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
}
} finally {
@@ -563,6 +577,22 @@
return true;
}
+ /**
+ * Start user, if its not already running, and bring it to foreground.
+ */
+ boolean startUserInForeground(final int userId, Dialog dlg) {
+ boolean result = startUser(userId, /* foreground */ true);
+ dlg.dismiss();
+ return result;
+ }
+
+ void showUserSwitchDialog(int userId, String userName) {
+ // The dialog will show and then initiate the user switch by calling startUserInForeground
+ Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
+ true /* above system */);
+ d.show();
+ }
+
void dispatchForegroundProfileChanged(int userId) {
final int observerCount = mUserSwitchObservers.beginBroadcast();
for (int i = 0; i < observerCount; i++) {
@@ -657,7 +687,7 @@
synchronized (mService) {
if (clearInitializing) {
uss.initializing = false;
- getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+ getUserManager().makeInitialized(uss.mHandle.getIdentifier());
}
if (clearSwitching) {
uss.switching = false;
@@ -683,8 +713,143 @@
mService.mStackSupervisor.resumeTopActivitiesLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
- getUserManagerLocked().onUserForeground(newUserId);
- mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ getUserManager().onUserForeground(newUserId);
+ sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ }
+
+ void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ Intent intent;
+ if (oldUserId >= 0) {
+ // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
+ List<UserInfo> profiles = getUserManager().getProfiles(oldUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+ }
+ }
+ if (newUserId >= 0) {
+ // Send USER_FOREGROUND broadcast to all profiles of the incoming user
+ List<UserInfo> profiles = getUserManager().getProfiles(newUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+ }
+ intent = new Intent(Intent.ACTION_USER_SWITCHED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null,
+ new String[] {android.Manifest.permission.MANAGE_USERS},
+ AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ UserHandle.USER_ALL);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+
+ int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+ int allowMode, String name, String callerPackage) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (callingUserId == userId) {
+ return userId;
+ }
+
+ // Note that we may be accessing mCurrentUserId outside of a lock...
+ // shouldn't be a big deal, if this is being called outside
+ // of a locked context there is intrinsically a race with
+ // the value the caller will receive and someone else changing it.
+ // We assume that USER_CURRENT_OR_SELF will use the current user; later
+ // we will switch to the calling user if access to the current user fails.
+ int targetUserId = unsafeConvertIncomingUserLocked(userId);
+
+ if (callingUid != 0 && callingUid != SYSTEM_UID) {
+ final boolean allow;
+ if (mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+ // If the caller has this permission, they always pass go. And collect $200.
+ allow = true;
+ } else if (allowMode == ALLOW_FULL_ONLY) {
+ // We require full access, sucks to be you.
+ allow = false;
+ } else if (mService.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+ callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+ // If the caller does not have either permission, they are always doomed.
+ allow = false;
+ } else if (allowMode == ALLOW_NON_FULL) {
+ // We are blanket allowing non-full access, you lucky caller!
+ allow = true;
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+ // We may or may not allow this depending on whether the two users are
+ // in the same profile.
+ allow = isSameProfileGroup(callingUserId, targetUserId);
+ } else {
+ throw new IllegalArgumentException("Unknown mode: " + allowMode);
+ }
+ if (!allow) {
+ if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+ // In this case, they would like to just execute as their
+ // owner user instead of failing.
+ targetUserId = callingUserId;
+ } else {
+ StringBuilder builder = new StringBuilder(128);
+ builder.append("Permission Denial: ");
+ builder.append(name);
+ if (callerPackage != null) {
+ builder.append(" from ");
+ builder.append(callerPackage);
+ }
+ builder.append(" asks to run as user ");
+ builder.append(userId);
+ builder.append(" but is calling from user ");
+ builder.append(UserHandle.getUserId(callingUid));
+ builder.append("; this requires ");
+ builder.append(INTERACT_ACROSS_USERS_FULL);
+ if (allowMode != ALLOW_FULL_ONLY) {
+ builder.append(" or ");
+ builder.append(INTERACT_ACROSS_USERS);
+ }
+ String msg = builder.toString();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ }
+ if (!allowAll && targetUserId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + targetUserId);
+ }
+ // Check shell permission
+ if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
+ if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
+ throw new SecurityException("Shell does not have permission to access user "
+ + targetUserId + "\n " + Debug.getCallers(3));
+ }
+ }
+ return targetUserId;
+ }
+
+ int unsafeConvertIncomingUserLocked(int userId) {
+ return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
+ ? getCurrentUserIdLocked(): userId;
}
void registerUserSwitchObserver(IUserSwitchObserver observer) {
@@ -705,7 +870,7 @@
mUserSwitchObservers.unregister(observer);
}
- UserState getStartedUserState(int userId) {
+ UserState getStartedUserStateLocked(int userId) {
return mStartedUsers.get(userId);
}
@@ -746,9 +911,8 @@
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
mService.broadcastIntentLocked(null, null, intent, null,
resultTo, 0, null, null,
- new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false,
- ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+ new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
}
@@ -759,7 +923,7 @@
* background.
*/
void updateCurrentProfileIdsLocked() {
- final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+ final List<UserInfo> profiles = getUserManager().getProfiles(mCurrentUserId,
false /* enabledOnly */);
int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
for (int i = 0; i < currentProfileIds.length; i++) {
@@ -769,7 +933,7 @@
synchronized (mUserProfileGroupIdsSelfLocked) {
mUserProfileGroupIdsSelfLocked.clear();
- final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+ final List<UserInfo> users = getUserManager().getUsers(false);
for (int i = 0; i < users.size(); i++) {
UserInfo user = users.get(i);
if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
@@ -783,6 +947,18 @@
return mStartedUserArray;
}
+ boolean isUserRunningLocked(int userId, boolean orStopped) {
+ UserState state = getStartedUserStateLocked(userId);
+ if (state == null) {
+ return false;
+ }
+ if (orStopped) {
+ return true;
+ }
+ return state.mState != UserState.STATE_STOPPING
+ && state.mState != UserState.STATE_SHUTDOWN;
+ }
+
UserInfo getCurrentUser() {
if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) && (
@@ -802,11 +978,50 @@
UserInfo getCurrentUserLocked() {
int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
- return getUserManagerLocked().getUserInfo(userId);
+ return getUserInfo(userId);
+ }
+
+ int getCurrentOrTargetUserIdLocked() {
+ return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
}
int getCurrentUserIdLocked() {
- return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+ return mCurrentUserId;
+ }
+
+ int setTargetUserIdLocked(int targetUserId) {
+ return mTargetUserId = targetUserId;
+ }
+
+ int[] getUsers() {
+ UserManagerService ums = getUserManager();
+ return ums != null ? ums.getUserIds() : new int[] { 0 };
+ }
+
+ UserInfo getUserInfo(int userId) {
+ return getUserManager().getUserInfo(userId);
+ }
+
+ int[] getUserIds() {
+ return getUserManager().getUserIds();
+ }
+
+ boolean exists(int userId) {
+ return getUserManager().exists(userId);
+ }
+
+ boolean hasUserRestriction(String restriction, int userId) {
+ return getUserManager().hasUserRestriction(restriction, userId);
+ }
+
+ Set<Integer> getProfileIds(int userId) {
+ Set<Integer> userIds = new HashSet<>();
+ final List<UserInfo> profiles = getUserManager().getProfiles(userId,
+ false /* enabledOnly */);
+ for (UserInfo user : profiles) {
+ userIds.add(user.id);
+ }
+ return userIds;
}
boolean isSameProfileGroup(int callingUserId, int targetUserId) {
@@ -824,6 +1039,10 @@
return ArrayUtils.contains(mCurrentProfileIds, userId);
}
+ int[] getCurrentProfileIdsLocked() {
+ return mCurrentProfileIds;
+ }
+
void dump(PrintWriter pw, boolean dumpAll) {
pw.println(" mStartedUsers:");
for (int i = 0; i < mStartedUsers.size(); i++) {
@@ -858,5 +1077,4 @@
}
}
}
-
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 5a66f4a..28b4096 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -97,7 +97,7 @@
void startUser() {
synchronized (this) {
if (!mStartedUser) {
- mService.startUserInForeground(mUserId, this);
+ mService.mUserController.startUserInForeground(mUserId, this);
mStartedUser = true;
final View decorView = getWindow().getDecorView();
if (decorView != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 66e731a..d89f47c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1756,7 +1756,8 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+ // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
+ if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
@@ -1848,7 +1849,8 @@
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
+ // If OP_MUTE_MICROPHONE is set, disallow unmuting.
+ if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java
index fe5c2da..2d768d8 100644
--- a/services/core/java/com/android/server/content/AppIdleMonitor.java
+++ b/services/core/java/com/android/server/content/AppIdleMonitor.java
@@ -50,8 +50,8 @@
}
}
- boolean isAppIdle(String packageName, int userId) {
- return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, userId);
+ boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+ return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, uidForAppId, userId);
}
@Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 15e5393..82e0eaf 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2625,9 +2625,18 @@
continue;
}
String packageName = getPackageName(op.target);
+ ApplicationInfo ai = null;
+ if (packageName != null) {
+ try {
+ ai = mContext.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+ } catch (NameNotFoundException e) {
+ }
+ }
// If app is considered idle, then skip for now and backoff
- if (packageName != null
- && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) {
+ if (ai != null
+ && mAppIdleMonitor.isAppIdle(packageName, ai.uid, op.target.userId)) {
increaseBackoffSetting(op);
op.appIdle = true;
if (isLoggable) {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 02d4f40..6fc02f6 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -67,7 +67,7 @@
mTrackedTasks.add(jobStatus);
String packageName = jobStatus.job.getService().getPackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- jobStatus.getUserId());
+ jobStatus.uId, jobStatus.getUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ packageName + " to " + appIdle);
@@ -108,7 +108,7 @@
for (JobStatus task : mTrackedTasks) {
String packageName = task.job.getService().getPackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- task.getUserId());
+ task.uId, task.getUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 17ae6dc..41aea04 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2227,7 +2227,7 @@
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- if (!mUsageStats.isAppIdle(packageName, userId)) {
+ if (!mUsageStats.isAppIdle(packageName, uid, userId)) {
return false;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 0d6e8d6..1cdc6db 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -26,6 +26,7 @@
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
@@ -41,6 +42,7 @@
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
/**
* Keeps track of notification activity, display, and user interaction.
@@ -367,26 +369,37 @@
break;
}
- for (String Key : n.extras.keySet()) {
- if (Notification.EXTRA_BIG_TEXT.equals(key)) {
- numWithBigText++;
- } else if (Notification.EXTRA_PICTURE.equals(key)) {
- numWithBigPicture++;
- } else if (Notification.EXTRA_LARGE_ICON.equals(key)) {
- numWithLargeIcon++;
- } else if (Notification.EXTRA_TEXT_LINES.equals(key)) {
- numWithInbox++;
- } else if (Notification.EXTRA_MEDIA_SESSION.equals(key)) {
- numWithMediaSession++;
- } else if (Notification.EXTRA_TITLE.equals(key)) {
- numWithTitle++;
- } else if (Notification.EXTRA_TEXT.equals(key)) {
- numWithText++;
- } else if (Notification.EXTRA_SUB_TEXT.equals(key)) {
- numWithSubText++;
- } else if (Notification.EXTRA_INFO_TEXT.equals(key)) {
- numWithInfoText++;
- }
+ final Set<String> names = n.extras.keySet();
+ if (names.contains(Notification.EXTRA_BIG_TEXT)) {
+ numWithBigText++;
+ }
+ if (names.contains(Notification.EXTRA_PICTURE)) {
+ numWithBigPicture++;
+ }
+ if (names.contains(Notification.EXTRA_LARGE_ICON)) {
+ numWithLargeIcon++;
+ }
+ if (names.contains(Notification.EXTRA_TEXT_LINES)) {
+ numWithInbox++;
+ }
+ if (names.contains(Notification.EXTRA_MEDIA_SESSION)) {
+ numWithMediaSession++;
+ }
+ if (names.contains(Notification.EXTRA_TITLE) &&
+ !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TITLE))) {
+ numWithTitle++;
+ }
+ if (names.contains(Notification.EXTRA_TEXT) &&
+ !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_TEXT))) {
+ numWithText++;
+ }
+ if (names.contains(Notification.EXTRA_SUB_TEXT) &&
+ !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_SUB_TEXT))) {
+ numWithSubText++;
+ }
+ if (names.contains(Notification.EXTRA_INFO_TEXT) &&
+ !TextUtils.isEmpty(n.extras.getCharSequence(Notification.EXTRA_INFO_TEXT))) {
+ numWithInfoText++;
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index d867616..150c849 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -53,6 +53,14 @@
mInstaller = new InstallerConnection();
}
+ /**
+ * Yell loudly if someone tries making future calls while holding a lock on
+ * the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ mInstaller.setWarnIfHeld(warnIfHeld);
+ }
+
@Override
public void onStart() {
Slog.i(TAG, "Waiting for installd to be ready.");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 22bedc3..08f9bd5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,6 +162,7 @@
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -2390,6 +2391,11 @@
// tidy.
Runtime.getRuntime().gc();
+ // The initial scanning above does many calls into installd while
+ // holding the mPackages lock, but we're mostly interested in yelling
+ // once we have a booted system.
+ mInstaller.setWarnIfHeld(mPackages);
+
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
}
@@ -15086,6 +15092,13 @@
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new PackageManagerShellCommand(this)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -16545,7 +16558,7 @@
*/
private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
final boolean DEBUG_CLEAN_APKS = false;
- int [] users = userManager.getUserIdsLPr();
+ int [] users = userManager.getUserIds();
Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
while (psit.hasNext()) {
PackageSetting ps = psit.next();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
new file mode 100644
index 0000000..c259ac2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -0,0 +1,529 @@
+package com.android.server.pm;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.WeakHashMap;
+
+class PackageManagerShellCommand extends ShellCommand {
+ final IPackageManager mInterface;
+ final private WeakHashMap<String, Resources> mResourceCache =
+ new WeakHashMap<String, Resources>();
+
+ PackageManagerShellCommand(PackageManagerService service) {
+ mInterface = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "list":
+ return runList();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runList() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to list");
+ return -1;
+ }
+ switch(type) {
+ case "features":
+ return runListFeatures();
+ case "instrumentation":
+ return runListInstrumentation();
+ case "libraries":
+ return runListLibraries();
+ case "package":
+ case "packages":
+ return runListPackages(false /*showSourceDir*/);
+ case "permission-groups":
+ return runListPermissionGroups();
+ case "permissions":
+ return runListPermissions();
+ }
+ pw.println("Error: unknown list type '" + type + "'");
+ return -1;
+ }
+
+ private int runListFeatures() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<FeatureInfo> list = new ArrayList<FeatureInfo>();
+ final FeatureInfo[] rawList = mInterface.getSystemAvailableFeatures();
+ for (int i=0; i<rawList.length; i++) {
+ list.add(rawList[i]);
+ }
+
+ // sort by name
+ Collections.sort(list, new Comparator<FeatureInfo>() {
+ public int compare(FeatureInfo o1, FeatureInfo o2) {
+ if (o1.name == o2.name) return 0;
+ if (o1.name == null) return -1;
+ if (o2.name == null) return 1;
+ return o1.name.compareTo(o2.name);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ FeatureInfo fi = list.get(p);
+ pw.print("feature:");
+ if (fi.name != null) pw.println(fi.name);
+ else pw.println("reqGlEsVersion=0x"
+ + Integer.toHexString(fi.reqGlEsVersion));
+ }
+ return 0;
+ }
+
+ private int runListInstrumentation() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean showSourceDir = false;
+ String targetPackage = null;
+
+ try {
+ String opt;
+ while ((opt = getNextArg()) != null) {
+ switch (opt) {
+ case "-f":
+ showSourceDir = true;
+ break;
+ default:
+ if (opt.charAt(0) != '-') {
+ targetPackage = opt;
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ break;
+ }
+ }
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex.toString());
+ return -1;
+ }
+
+ final List<InstrumentationInfo> list =
+ mInterface.queryInstrumentation(targetPackage, 0 /*flags*/);
+
+ // sort by target package
+ Collections.sort(list, new Comparator<InstrumentationInfo>() {
+ public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
+ return o1.targetPackage.compareTo(o2.targetPackage);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ final InstrumentationInfo ii = list.get(p);
+ pw.print("instrumentation:");
+ if (showSourceDir) {
+ pw.print(ii.sourceDir);
+ pw.print("=");
+ }
+ final ComponentName cn = new ComponentName(ii.packageName, ii.name);
+ pw.print(cn.flattenToShortString());
+ pw.print(" (target=");
+ pw.print(ii.targetPackage);
+ pw.println(")");
+ }
+ return 0;
+ }
+
+ private int runListLibraries() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<String> list = new ArrayList<String>();
+ final String[] rawList = mInterface.getSystemSharedLibraryNames();
+ for (int i = 0; i < rawList.length; i++) {
+ list.add(rawList[i]);
+ }
+
+ // sort by name
+ Collections.sort(list, new Comparator<String>() {
+ public int compare(String o1, String o2) {
+ if (o1 == o2) return 0;
+ if (o1 == null) return -1;
+ if (o2 == null) return 1;
+ return o1.compareTo(o2);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ String lib = list.get(p);
+ pw.print("library:");
+ pw.println(lib);
+ }
+ return 0;
+ }
+
+ private int runListPackages(boolean showSourceDir) throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int getFlags = 0;
+ boolean listDisabled = false, listEnabled = false;
+ boolean listSystem = false, listThirdParty = false;
+ boolean listInstaller = false;
+ int userId = UserHandle.USER_SYSTEM;
+ try {
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-d":
+ listDisabled = true;
+ break;
+ case "-e":
+ listEnabled = true;
+ break;
+ case "-f":
+ showSourceDir = true;
+ break;
+ case "-i":
+ listInstaller = true;
+ break;
+ case "-l":
+ // old compat
+ break;
+ case "-lf":
+ showSourceDir = true;
+ break;
+ case "-s":
+ listSystem = true;
+ break;
+ case "-u":
+ getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
+ break;
+ case "-3":
+ listThirdParty = true;
+ break;
+ case "--user":
+ userId = Integer.parseInt(getNextArg());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex.toString());
+ return -1;
+ }
+
+ final String filter = getNextArg();
+
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice<PackageInfo> slice =
+ mInterface.getInstalledPackages(getFlags, userId);
+ final List<PackageInfo> packages = slice.getList();
+
+ final int count = packages.size();
+ for (int p = 0; p < count; p++) {
+ final PackageInfo info = packages.get(p);
+ if (filter != null && !info.packageName.contains(filter)) {
+ continue;
+ }
+ final boolean isSystem =
+ (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
+ if ((!listDisabled || !info.applicationInfo.enabled) &&
+ (!listEnabled || info.applicationInfo.enabled) &&
+ (!listSystem || isSystem) &&
+ (!listThirdParty || !isSystem)) {
+ pw.print("package:");
+ if (showSourceDir) {
+ pw.print(info.applicationInfo.sourceDir);
+ pw.print("=");
+ }
+ pw.print(info.packageName);
+ if (listInstaller) {
+ pw.print(" installer=");
+ pw.print(mInterface.getInstallerPackageName(info.packageName));
+ }
+ pw.println();
+ }
+ }
+ return 0;
+ }
+
+ private int runListPermissionGroups() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<PermissionGroupInfo> pgs = mInterface.getAllPermissionGroups(0);
+
+ final int count = pgs.size();
+ for (int p = 0; p < count ; p++) {
+ final PermissionGroupInfo pgi = pgs.get(p);
+ pw.print("permission group:");
+ pw.println(pgi.name);
+ }
+ return 0;
+ }
+
+ private int runListPermissions() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean labels = false;
+ boolean groups = false;
+ boolean userOnly = false;
+ boolean summary = false;
+ boolean dangerousOnly = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-d":
+ dangerousOnly = true;
+ break;
+ case "-f":
+ labels = true;
+ break;
+ case "-g":
+ groups = true;
+ break;
+ case "-s":
+ groups = true;
+ labels = true;
+ summary = true;
+ break;
+ case "-u":
+ userOnly = true;
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final ArrayList<String> groupList = new ArrayList<String>();
+ if (groups) {
+ final List<PermissionGroupInfo> infos =
+ mInterface.getAllPermissionGroups(0 /*flags*/);
+ final int count = infos.size();
+ for (int i = 0; i < count; i++) {
+ groupList.add(infos.get(i).name);
+ }
+ groupList.add(null);
+ } else {
+ final String grp = getNextArg();
+ groupList.add(grp);
+ }
+
+ if (dangerousOnly) {
+ pw.println("Dangerous Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_DANGEROUS,
+ PermissionInfo.PROTECTION_DANGEROUS);
+ if (userOnly) {
+ pw.println("Normal Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_NORMAL,
+ PermissionInfo.PROTECTION_NORMAL);
+ }
+ } else if (userOnly) {
+ pw.println("Dangerous and Normal Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_NORMAL,
+ PermissionInfo.PROTECTION_DANGEROUS);
+ } else {
+ pw.println("All Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ -10000, 10000);
+ }
+ return 0;
+ }
+
+ private void doListPermissions(ArrayList<String> groupList, boolean groups, boolean labels,
+ boolean summary, int startProtectionLevel, int endProtectionLevel)
+ throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int groupCount = groupList.size();
+ for (int i = 0; i < groupCount; i++) {
+ String groupName = groupList.get(i);
+ String prefix = "";
+ if (groups) {
+ if (i > 0) {
+ pw.println("");
+ }
+ if (groupName != null) {
+ PermissionGroupInfo pgi =
+ mInterface.getPermissionGroupInfo(groupName, 0 /*flags*/);
+ if (summary) {
+ Resources res = getResources(pgi);
+ if (res != null) {
+ pw.print(loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel) + ": ");
+ } else {
+ pw.print(pgi.name + ": ");
+
+ }
+ } else {
+ pw.println((labels ? "+ " : "") + "group:" + pgi.name);
+ if (labels) {
+ pw.println(" package:" + pgi.packageName);
+ Resources res = getResources(pgi);
+ if (res != null) {
+ pw.println(" label:"
+ + loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel));
+ pw.println(" description:"
+ + loadText(pgi, pgi.descriptionRes,
+ pgi.nonLocalizedDescription));
+ }
+ }
+ }
+ } else {
+ pw.println(((labels && !summary) ? "+ " : "") + "ungrouped:");
+ }
+ prefix = " ";
+ }
+ List<PermissionInfo> ps =
+ mInterface.queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
+ final int count = ps.size();
+ boolean first = true;
+ for (int p = 0 ; p < count ; p++) {
+ PermissionInfo pi = ps.get(p);
+ if (groups && groupName == null && pi.group != null) {
+ continue;
+ }
+ final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ if (base < startProtectionLevel
+ || base > endProtectionLevel) {
+ continue;
+ }
+ if (summary) {
+ if (first) {
+ first = false;
+ } else {
+ pw.print(", ");
+ }
+ Resources res = getResources(pi);
+ if (res != null) {
+ pw.print(loadText(pi, pi.labelRes,
+ pi.nonLocalizedLabel));
+ } else {
+ pw.print(pi.name);
+ }
+ } else {
+ pw.println(prefix + (labels ? "+ " : "")
+ + "permission:" + pi.name);
+ if (labels) {
+ pw.println(prefix + " package:" + pi.packageName);
+ Resources res = getResources(pi);
+ if (res != null) {
+ pw.println(prefix + " label:"
+ + loadText(pi, pi.labelRes,
+ pi.nonLocalizedLabel));
+ pw.println(prefix + " description:"
+ + loadText(pi, pi.descriptionRes,
+ pi.nonLocalizedDescription));
+ }
+ pw.println(prefix + " protectionLevel:"
+ + PermissionInfo.protectionToString(pi.protectionLevel));
+ }
+ }
+ }
+
+ if (summary) {
+ pw.println("");
+ }
+ }
+ }
+
+ private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized)
+ throws RemoteException {
+ if (nonLocalized != null) {
+ return nonLocalized.toString();
+ }
+ if (res != 0) {
+ Resources r = getResources(pii);
+ if (r != null) {
+ try {
+ return r.getString(res);
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+ }
+ return null;
+ }
+
+ private Resources getResources(PackageItemInfo pii) throws RemoteException {
+ Resources res = mResourceCache.get(pii.packageName);
+ if (res != null) return res;
+
+ ApplicationInfo ai = mInterface.getApplicationInfo(pii.packageName, 0, 0);
+ AssetManager am = new AssetManager();
+ am.addAssetPath(ai.publicSourceDir);
+ res = new Resources(am, null, null);
+ mResourceCache.put(pii.packageName, res);
+ return res;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Package manager (package) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" list features");
+ pw.println(" Prints all features of the system.");
+ pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
+ pw.println(" Prints all test packages; optionally only those targetting TARGET-PACKAGE");
+ pw.println(" Options:");
+ pw.println(" -f: dump the name of the .apk file containing the test package");
+ pw.println(" list libraries");
+ pw.println(" Prints all system libraries.");
+ pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
+ pw.println(" Prints all packages; optionally only those whose name contains");
+ pw.println(" the text in FILTER.");
+ pw.println(" Options:");
+ pw.println(" -f: see their associated file");
+ pw.println(" -d: filter to only show disbled packages");
+ pw.println(" -e: filter to only show enabled packages");
+ pw.println(" -s: filter to only show system packages");
+ pw.println(" -3: filter to only show third party packages");
+ pw.println(" -i: see the installer for the packages");
+ pw.println(" -u: also include uninstalled packages");
+ pw.println(" list permission-groups");
+ pw.println(" Prints all known permission groups.");
+ pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
+ pw.println(" Prints all known permissions; optionally only those in GROUP.");
+ pw.println(" Options:");
+ pw.println(" -g: organize by group");
+ pw.println(" -f: print all information");
+ pw.println(" -s: short summary");
+ pw.println(" -d: only list dangerous permissions");
+ pw.println(" -u: list only the permissions users will see");
+ pw.println("");
+ }
+}
+
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index bbdfe31..78328f5 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -239,6 +239,7 @@
keySetData = base.keySetData;
verificationInfo = base.verificationInfo;
installerPackageName = base.installerPackageName;
+ volumeUuid = base.volumeUuid;
}
private PackageUserState modifyUserState(int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4dd7388..c41d493 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -44,7 +45,10 @@
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
@@ -91,13 +95,15 @@
* Service for {@link UserManager}.
*
* Method naming convention:
- * - Methods suffixed with "Locked" should be called within the {@code this} lock.
- * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ * <ul>
+ * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
+ * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
+ * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
+ * </ul>
*/
public class UserManagerService extends IUserManager.Stub {
-
private static final String LOG_TAG = "UserManagerService";
-
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
@@ -150,11 +156,6 @@
// without first making sure that the rest of the framework is prepared for it.
private static final int MAX_MANAGED_PROFILES = 1;
- /**
- * Flag indicating whether device credentials are shared among same-user profiles.
- */
- private static final boolean CONFIG_PROFILES_SHARE_CREDENTIAL = true;
-
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
@@ -164,15 +165,17 @@
private final PackageManagerService mPm;
private final Object mInstallLock;
private final Object mPackagesLock;
+ // Short-term lock for internal state, when interaction/sync with PM is not required
+ private final Object mUsersLock = new Object();
+ private final Object mRestrictionsLock = new Object();
private final Handler mHandler;
private final File mUsersDir;
private final File mUserListFile;
- private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
-
- private final Object mRestrictionsLock = new Object();
+ @GuardedBy("mUsersLock")
+ private final SparseArray<UserInfo> mUsers = new SparseArray<>();
/**
* User restrictions set via UserManager. This doesn't include restrictions set by
@@ -183,7 +186,7 @@
* maybe shared between {@link #mBaseUserRestrictions} and
* {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
* (Otherwise we won't be able to detect what restrictions have changed in
- * {@link #updateUserRestrictionsInternalRL).
+ * {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
@@ -198,20 +201,29 @@
* maybe shared between {@link #mBaseUserRestrictions} and
* {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
* (Otherwise we won't be able to detect what restrictions have changed in
- * {@link #updateUserRestrictionsInternalRL).
+ * {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
+ /**
+ * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}. We
+ * use it to detect restrictions that have changed since the last
+ * {@link #applyUserRestrictionsLR} call.
+ */
+ @GuardedBy("mRestrictionsLock")
+ private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+
private final Bundle mGuestRestrictions = new Bundle();
/**
* Set of user IDs being actively removed. Removed IDs linger in this set
* for several seconds to work around a VFS caching issue.
*/
- // @GuardedBy("mPackagesLock")
+ @GuardedBy("mUsersLock")
private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
+ @GuardedBy("mUsersLock")
private int[] mUserIds;
private int mNextSerialNumber;
private int mUserVersion = 0;
@@ -271,7 +283,7 @@
-1, -1);
mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
initDefaultGuestRestrictions();
- readUserListLocked();
+ readUserListLILP();
sInstance = this;
}
}
@@ -282,21 +294,23 @@
void systemReady() {
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
- // Prune out any partially created/partially removed users.
- ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo ui = mUsers.valueAt(i);
- if ((ui.partial || ui.guestToRemove) && i != 0) {
- partials.add(ui);
+ synchronized (mUsersLock) {
+ // Prune out any partially created/partially removed users.
+ ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i);
+ if ((ui.partial || ui.guestToRemove) && i != 0) {
+ partials.add(ui);
+ }
}
- }
- final int partialsSize = partials.size();
- for (int i = 0; i < partialsSize; i++) {
- UserInfo ui = partials.get(i);
- Slog.w(LOG_TAG, "Removing partially created user " + ui.id
- + " (name=" + ui.name + ")");
- removeUserStateLocked(ui.id);
+ final int partialsSize = partials.size();
+ for (int i = 0; i < partialsSize; i++) {
+ UserInfo ui = partials.get(i);
+ Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+ + " (name=" + ui.name + ")");
+ removeUserStateLILP(ui.id);
+ }
}
}
}
@@ -316,7 +330,7 @@
@Override
public UserInfo getPrimaryUser() {
checkManageUsersPermission("query users");
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
@@ -331,7 +345,7 @@
@Override
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
checkManageUsersPermission("query users");
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
@@ -354,8 +368,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mPackagesLock) {
- return getProfilesLocked(userId, enabledOnly);
+ synchronized (mUsersLock) {
+ return getProfilesLU(userId, enabledOnly);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -363,8 +377,8 @@
}
/** Assume permissions already checked and caller's identity cleared */
- private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
- UserInfo user = getUserInfoLocked(userId);
+ private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly) {
+ UserInfo user = getUserInfoLU(userId);
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
if (user == null) {
// Probably a dying user
@@ -390,9 +404,9 @@
@Override
public int getCredentialOwnerProfile(int userHandle) {
checkManageUsersPermission("get the credential owner");
- if (CONFIG_PROFILES_SHARE_CREDENTIAL) {
- synchronized (mPackagesLock) {
- UserInfo profileParent = getProfileParentLocked(userHandle);
+ if (!"file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
+ synchronized (mUsersLock) {
+ UserInfo profileParent = getProfileParentLU(userHandle);
if (profileParent != null) {
return profileParent.id;
}
@@ -407,32 +421,35 @@
if (userId == otherUserId) return true;
checkManageUsersPermission("check if in the same profile group");
synchronized (mPackagesLock) {
- return isSameProfileGroupLocked(userId, otherUserId);
+ return isSameProfileGroupLP(userId, otherUserId);
}
}
- private boolean isSameProfileGroupLocked(int userId, int otherUserId) {
- UserInfo userInfo = getUserInfoLocked(userId);
- if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- return false;
+ private boolean isSameProfileGroupLP(int userId, int otherUserId) {
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return false;
+ }
+ UserInfo otherUserInfo = getUserInfoLU(otherUserId);
+ if (otherUserInfo == null
+ || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return false;
+ }
+ return userInfo.profileGroupId == otherUserInfo.profileGroupId;
}
- UserInfo otherUserInfo = getUserInfoLocked(otherUserId);
- if (otherUserInfo == null || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- return false;
- }
- return userInfo.profileGroupId == otherUserInfo.profileGroupId;
}
@Override
public UserInfo getProfileParent(int userHandle) {
checkManageUsersPermission("get the profile parent");
- synchronized (mPackagesLock) {
- return getProfileParentLocked(userHandle);
+ synchronized (mUsersLock) {
+ return getProfileParentLU(userHandle);
}
}
- private UserInfo getProfileParentLocked(int userHandle) {
- UserInfo profile = getUserInfoLocked(userHandle);
+ private UserInfo getProfileParentLU(int userHandle) {
+ UserInfo profile = getUserInfoLU(userHandle);
if (profile == null) {
return null;
}
@@ -440,11 +457,11 @@
if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
return null;
} else {
- return getUserInfoLocked(parentUserId);
+ return getUserInfoLU(parentUserId);
}
}
- private boolean isProfileOf(UserInfo user, UserInfo profile) {
+ private static boolean isProfileOf(UserInfo user, UserInfo profile) {
return user.id == profile.id ||
(user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
&& user.profileGroupId == profile.profileGroupId);
@@ -454,10 +471,13 @@
public void setUserEnabled(int userId) {
checkManageUsersPermission("enable user");
synchronized (mPackagesLock) {
- UserInfo info = getUserInfoLocked(userId);
+ UserInfo info;
+ synchronized (mUsersLock) {
+ info = getUserInfoLU(userId);
+ }
if (info != null && !info.isEnabled()) {
info.flags ^= UserInfo.FLAG_DISABLED;
- writeUserLocked(info);
+ writeUserLP(info);
}
}
}
@@ -465,23 +485,23 @@
@Override
public UserInfo getUserInfo(int userId) {
checkManageUsersPermission("query user");
- synchronized (mPackagesLock) {
- return getUserInfoLocked(userId);
+ synchronized (mUsersLock) {
+ return getUserInfoLU(userId);
}
}
@Override
public boolean isRestricted() {
- synchronized (mPackagesLock) {
- return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+ synchronized (mUsersLock) {
+ return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted();
}
}
@Override
public boolean canHaveRestrictedProfile(int userId) {
checkManageUsersPermission("canHaveRestrictedProfile");
- synchronized (mPackagesLock) {
- final UserInfo userInfo = getUserInfoLocked(userId);
+ synchronized (mUsersLock) {
+ final UserInfo userInfo = getUserInfoLU(userId);
if (userInfo == null || !userInfo.canHaveProfile()) {
return false;
}
@@ -498,7 +518,7 @@
/*
* Should be locked on mUsers before calling this.
*/
- private UserInfo getUserInfoLocked(int userId) {
+ private UserInfo getUserInfoLU(int userId) {
UserInfo ui = mUsers.get(userId);
// If it is partial and not in the process of being removed, return as unknown user.
if (ui != null && ui.partial && !mRemovingUserIds.get(userId)) {
@@ -508,11 +528,19 @@
return ui;
}
+ /**
+ * Obtains {@link #mUsersLock} and return UserInfo from mUsers.
+ * <p>No permissions checking or any addition checks are made</p>
+ */
+ private UserInfo getUserInfoNoChecks(int userId) {
+ synchronized (mUsersLock) {
+ return mUsers.get(userId);
+ }
+ }
+
/** Called by PackageManagerService */
public boolean exists(int userId) {
- synchronized (mPackagesLock) {
- return mUsers.get(userId) != null;
- }
+ return getUserInfoNoChecks(userId) != null;
}
@Override
@@ -520,14 +548,14 @@
checkManageUsersPermission("rename users");
boolean changed = false;
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
return;
}
if (name != null && !name.equals(info.name)) {
info.name = name;
- writeUserLocked(info);
+ writeUserLP(info);
changed = true;
}
}
@@ -542,13 +570,13 @@
long ident = Binder.clearCallingIdentity();
try {
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
return;
}
- writeBitmapLocked(info, bitmap);
- writeUserLocked(info);
+ writeBitmapLP(info, bitmap);
+ writeUserLP(info);
}
sendUserInfoChangedBroadcast(userId);
} finally {
@@ -567,12 +595,12 @@
public ParcelFileDescriptor getUserIcon(int userId) {
String iconPath;
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "getUserIcon: unknown user #" + userId);
return null;
}
- int callingGroupId = mUsers.get(UserHandle.getCallingUserId()).profileGroupId;
+ int callingGroupId = getUserInfoNoChecks(UserHandle.getCallingUserId()).profileGroupId;
if (callingGroupId == UserInfo.NO_PROFILE_GROUP_ID
|| callingGroupId != info.profileGroupId) {
checkManageUsersPermission("get the icon of a user who is not related");
@@ -595,13 +623,14 @@
public void makeInitialized(int userId) {
checkManageUsersPermission("makeInitialized");
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
+ // TODO Check if we should return here instead of a null check below
}
- if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+ if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLocked(info);
+ scheduleWriteUserLP(info);
}
}
}
@@ -620,6 +649,7 @@
@Override
public Bundle getDefaultGuestRestrictions() {
checkManageUsersPermission("getDefaultGuestRestrictions");
+ // TODO Switch to mGuestRestrictions for locking
synchronized (mPackagesLock) {
return new Bundle(mGuestRestrictions);
}
@@ -628,15 +658,17 @@
@Override
public void setDefaultGuestRestrictions(Bundle restrictions) {
checkManageUsersPermission("setDefaultGuestRestrictions");
- synchronized (mPackagesLock) {
- mGuestRestrictions.clear();
- mGuestRestrictions.putAll(restrictions);
- writeUserListLocked();
+ synchronized (mInstallLock) {
+ synchronized (mPackagesLock) {
+ mGuestRestrictions.clear();
+ mGuestRestrictions.putAll(restrictions);
+ writeUserListLILP();
+ }
}
}
@GuardedBy("mRestrictionsLock")
- private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+ private Bundle computeEffectiveUserRestrictionsLR(int userId) {
final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
@@ -652,7 +684,7 @@
}
@GuardedBy("mRestrictionsLock")
- private void invalidateEffectiveUserRestrictionsRL(int userId) {
+ private void invalidateEffectiveUserRestrictionsLR(int userId) {
if (DBG) {
Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
}
@@ -663,7 +695,7 @@
synchronized (mRestrictionsLock) {
Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
if (restrictions == null) {
- restrictions = computeEffectiveUserRestrictionsRL(userId);
+ restrictions = computeEffectiveUserRestrictionsLR(userId);
mCachedEffectiveUserRestrictions.put(userId, restrictions);
}
return restrictions;
@@ -709,7 +741,7 @@
UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
newRestrictions.putBoolean(key, value);
- updateUserRestrictionsInternalRL(newRestrictions, userId);
+ updateUserRestrictionsInternalLR(newRestrictions, userId);
}
}
@@ -725,14 +757,12 @@
* @param userId target user ID.
*/
@GuardedBy("mRestrictionsLock")
- private void updateUserRestrictionsInternalRL(
+ private void updateUserRestrictionsInternalLR(
@Nullable Bundle newRestrictions, int userId) {
if (DBG) {
Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
+ " bundle=" + newRestrictions);
}
- final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
-
// Update system restrictions.
if (newRestrictions != null) {
// If newRestrictions == the current one, it's probably a bug.
@@ -742,15 +772,22 @@
mBaseUserRestrictions.put(userId, newRestrictions);
}
- mCachedEffectiveUserRestrictions.put(
- userId, computeEffectiveUserRestrictionsRL(userId));
+ final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
- applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
+ mCachedEffectiveUserRestrictions.put(userId, effective);
+
+ applyUserRestrictionsLR(userId, effective);
}
@GuardedBy("mRestrictionsLock")
- private void applyUserRestrictionsRL(int userId,
- Bundle newRestrictions, Bundle prevRestrictions) {
+ private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
+ final Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
+
+ if (DBG) {
+ Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
+ + " new=" + newRestrictions + " prev=" + prevRestrictions);
+ }
+
final long token = Binder.clearCallingIdentity();
try {
mAppOpsService.setUserRestrictions(newRestrictions, userId);
@@ -760,20 +797,22 @@
Binder.restoreCallingIdentity(token);
}
- // TODO Move the code from DPMS.setUserRestriction().
+ UserRestrictionsUtils.applyUserRestrictions(
+ mContext, userId, newRestrictions, prevRestrictions);
+
+ mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
}
@GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsRL(int userId) {
- updateUserRestrictionsInternalRL(null, userId);
+ private void updateEffectiveUserRestrictionsLR(int userId) {
+ updateUserRestrictionsInternalLR(null, userId);
}
@GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsForAllUsersRL() {
+ private void updateEffectiveUserRestrictionsForAllUsersLR() {
// First, invalidate all cached values.
- synchronized (mRestrictionsLock) {
- mCachedEffectiveUserRestrictions.clear();
- }
+ mCachedEffectiveUserRestrictions.clear();
+
// We don't want to call into ActivityManagerNative while taking a lock, so we'll call
// it on a handler.
final Runnable r = new Runnable() {
@@ -794,7 +833,7 @@
// TODO: "Apply restrictions upon user start hasn't been implemented. Implement it.
synchronized (mRestrictionsLock) {
for (int i = 0; i < runningUsers.length; i++) {
- updateUserRestrictionsInternalRL(null, runningUsers[i]);
+ updateUserRestrictionsInternalLR(null, runningUsers[i]);
}
}
}
@@ -805,8 +844,12 @@
/**
* Check if we've hit the limit of how many users can be created.
*/
- private boolean isUserLimitReachedLocked() {
- return getAliveUsersExcludingGuestsCountLocked() >= UserManager.getMaxSupportedUsers();
+ private boolean isUserLimitReached() {
+ int count;
+ synchronized (mUsersLock) {
+ count = getAliveUsersExcludingGuestsCountLU();
+ }
+ return count >= UserManager.getMaxSupportedUsers();
}
@Override
@@ -824,18 +867,18 @@
if (managedProfilesCount >= MAX_MANAGED_PROFILES) {
return false;
}
- synchronized(mPackagesLock) {
- UserInfo userInfo = getUserInfoLocked(userId);
+ synchronized(mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
if (!userInfo.canHaveProfile()) {
return false;
}
- int usersCount = getAliveUsersExcludingGuestsCountLocked();
+ int usersCount = getAliveUsersExcludingGuestsCountLU();
// We allow creating a managed profile in the special case where there is only one user.
return usersCount == 1 || usersCount < UserManager.getMaxSupportedUsers();
}
}
- private int getAliveUsersExcludingGuestsCountLocked() {
+ private int getAliveUsersExcludingGuestsCountLU() {
int aliveUserCount = 0;
final int totalUserCount = mUsers.size();
// Skip over users being removed
@@ -874,7 +917,7 @@
}
}
- private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
+ private void writeBitmapLP(UserInfo info, Bitmap bitmap) {
try {
File dir = new File(mUsersDir, Integer.toString(info.id));
File file = new File(dir, USER_PHOTO_FILENAME);
@@ -908,18 +951,14 @@
* @return the array of user ids.
*/
public int[] getUserIds() {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
return mUserIds;
}
}
- int[] getUserIdsLPr() {
- return mUserIds;
- }
-
- private void readUserListLocked() {
+ private void readUserListLILP() {
if (!mUserListFile.exists()) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
return;
}
FileInputStream fis = null;
@@ -936,7 +975,7 @@
if (type != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
return;
}
@@ -957,12 +996,14 @@
final String name = parser.getName();
if (name.equals(TAG_USER)) {
String id = parser.getAttributeValue(null, ATTR_ID);
- UserInfo user = readUserLocked(Integer.parseInt(id));
+ UserInfo user = readUserLILP(Integer.parseInt(id));
if (user != null) {
- mUsers.put(user.id, user);
- if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
- mNextSerialNumber = user.id + 1;
+ synchronized (mUsersLock) {
+ mUsers.put(user.id, user);
+ if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
+ mNextSerialNumber = user.id + 1;
+ }
}
}
} else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
@@ -979,12 +1020,12 @@
}
}
}
- updateUserIdsLocked();
- upgradeIfNecessaryLocked();
+ updateUserIds();
+ upgradeIfNecessaryLILP();
} catch (IOException ioe) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
} catch (XmlPullParserException pe) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
} finally {
if (fis != null) {
try {
@@ -998,24 +1039,24 @@
/**
* Upgrade steps between versions, either for fixing bugs or changing the data format.
*/
- private void upgradeIfNecessaryLocked() {
+ private void upgradeIfNecessaryLILP() {
int userVersion = mUserVersion;
if (userVersion < 1) {
// Assign a proper name for the owner, if not initialized correctly before
- UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+ UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ("Primary".equals(user.name)) {
user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
userVersion = 1;
}
if (userVersion < 2) {
// Owner should be marked as initialized
- UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+ UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
user.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
userVersion = 2;
}
@@ -1032,13 +1073,15 @@
if (userVersion < 6) {
final boolean splitSystemUser = UserManager.isSplitSystemUser();
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo user = mUsers.valueAt(i);
- // In non-split mode, only user 0 can have restricted profiles
- if (!splitSystemUser && user.isRestricted()
- && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
- user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
- scheduleWriteUserLocked(user);
+ synchronized (mUsersLock) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ // In non-split mode, only user 0 can have restricted profiles
+ if (!splitSystemUser && user.isRestricted()
+ && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
+ user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
+ scheduleWriteUserLP(user);
+ }
}
}
userVersion = 6;
@@ -1049,11 +1092,11 @@
+ USER_VERSION);
} else {
mUserVersion = userVersion;
- writeUserListLocked();
+ writeUserListLILP();
}
}
- private void fallbackToSingleUserLocked() {
+ private void fallbackToSingleUserLILP() {
int flags = UserInfo.FLAG_INITIALIZED;
// In split system user mode, the admin and primary flags are assigned to the first human
// user.
@@ -1064,7 +1107,9 @@
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM,
mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
flags);
- mUsers.put(system.id, system);
+ synchronized (mUsersLock) {
+ mUsers.put(system.id, system);
+ }
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
@@ -1073,14 +1118,14 @@
mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
}
- updateUserIdsLocked();
+ updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLocked();
- writeUserLocked(system);
+ writeUserListLILP();
+ writeUserLP(system);
}
- private void scheduleWriteUserLocked(UserInfo userInfo) {
+ private void scheduleWriteUserLP(UserInfo userInfo) {
if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
@@ -1094,7 +1139,7 @@
* <name>Primary</name>
* </user>
*/
- private void writeUserLocked(UserInfo userInfo) {
+ private void writeUserLP(UserInfo userInfo) {
FileOutputStream fos = null;
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
try {
@@ -1160,7 +1205,8 @@
* <user id="2"></user>
* </users>
*/
- private void writeUserListLocked() {
+ private void writeUserListLILP() {
+ // TODO Investigate removing a dependency on mInstallLock
FileOutputStream fos = null;
AtomicFile userListFile = new AtomicFile(mUserListFile);
try {
@@ -1181,11 +1227,17 @@
UserRestrictionsUtils
.writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo user = mUsers.valueAt(i);
+ int[] userIdsToWrite;
+ synchronized (mUsersLock) {
+ userIdsToWrite = new int[mUsers.size()];
+ for (int i = 0; i < userIdsToWrite.length; i++) {
+ UserInfo user = mUsers.valueAt(i);
+ userIdsToWrite[i] = user.id;
+ }
+ }
+ for (int id : userIdsToWrite) {
serializer.startTag(null, TAG_USER);
- serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
+ serializer.attribute(null, ATTR_ID, Integer.toString(id));
serializer.endTag(null, TAG_USER);
}
@@ -1199,7 +1251,7 @@
}
}
- private UserInfo readUserLocked(int id) {
+ private UserInfo readUserLILP(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -1393,20 +1445,22 @@
synchronized (mPackagesLock) {
UserInfo parent = null;
if (parentId != UserHandle.USER_NULL) {
- parent = getUserInfoLocked(parentId);
+ synchronized (mUsersLock) {
+ parent = getUserInfoLU(parentId);
+ }
if (parent == null) return null;
}
if (isManagedProfile && !canAddMoreManagedProfiles(parentId)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
- if (!isGuest && !isManagedProfile && isUserLimitReachedLocked()) {
+ if (!isGuest && !isManagedProfile && isUserLimitReached()) {
// If we're not adding a guest user or a managed profile and the limit has
// been reached, cannot add a user.
return null;
}
// If we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUserLocked() != null) {
+ if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
@@ -1440,7 +1494,7 @@
flags |= UserInfo.FLAG_ADMIN;
}
}
- userId = getNextAvailableIdLocked();
+ userId = getNextAvailableId();
userInfo = new UserInfo(userId, name, null, flags);
userInfo.serialNumber = mNextSerialNumber++;
long now = System.currentTimeMillis();
@@ -1448,12 +1502,12 @@
userInfo.partial = true;
Environment.getUserSystemDirectory(userInfo.id).mkdirs();
mUsers.put(userId, userInfo);
- writeUserListLocked();
+ writeUserListLILP();
if (parent != null) {
if (isManagedProfile) {
if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.profileGroupId = parent.id;
- scheduleWriteUserLocked(parent);
+ scheduleWriteUserLP(parent);
}
userInfo.profileGroupId = parent.profileGroupId;
} else if (isRestricted) {
@@ -1462,7 +1516,7 @@
}
if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.restrictedProfileParentId = parent.id;
- scheduleWriteUserLocked(parent);
+ scheduleWriteUserLP(parent);
}
userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
@@ -1481,8 +1535,8 @@
}
mPm.createNewUserLILPw(userId);
userInfo.partial = false;
- scheduleWriteUserLocked(userInfo);
- updateUserIdsLocked();
+ scheduleWriteUserLP(userInfo);
+ updateUserIds();
Bundle restrictions = new Bundle();
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
@@ -1525,12 +1579,14 @@
* Find the current guest user. If the Guest user is partial,
* then do not include it in the results as it is about to die.
*/
- private UserInfo findCurrentGuestUserLocked() {
- final int size = mUsers.size();
- for (int i = 0; i < size; i++) {
- final UserInfo user = mUsers.valueAt(i);
- if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
- return user;
+ private UserInfo findCurrentGuestUser() {
+ synchronized (mUsersLock) {
+ final int size = mUsers.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo user = mUsers.valueAt(i);
+ if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
+ return user;
+ }
}
}
return null;
@@ -1554,9 +1610,11 @@
try {
final UserInfo user;
synchronized (mPackagesLock) {
- user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
- return false;
+ synchronized (mUsersLock) {
+ user = mUsers.get(userHandle);
+ if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+ return false;
+ }
}
if (!user.isGuest()) {
return false;
@@ -1570,7 +1628,7 @@
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
user.flags |= UserInfo.FLAG_DISABLED;
- writeUserLocked(user);
+ writeUserLP(user);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1600,15 +1658,17 @@
return false;
}
synchronized (mPackagesLock) {
- user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
- return false;
- }
+ synchronized (mUsersLock) {
+ user = mUsers.get(userHandle);
+ if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+ return false;
+ }
- // We remember deleted user IDs to prevent them from being
- // reused during the current boot; they can still be reused
- // after a reboot.
- mRemovingUserIds.put(userHandle, true);
+ // We remember deleted user IDs to prevent them from being
+ // reused during the current boot; they can still be reused
+ // after a reboot.
+ mRemovingUserIds.put(userHandle, true);
+ }
try {
mAppOpsService.removeUser(userHandle);
@@ -1622,7 +1682,7 @@
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
user.flags |= UserInfo.FLAG_DISABLED;
- writeUserLocked(user);
+ writeUserLP(user);
}
if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -1680,7 +1740,7 @@
.onUserRemoved(userHandle);
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
- removeUserStateLocked(userHandle);
+ removeUserStateLILP(userHandle);
}
}
}
@@ -1694,20 +1754,22 @@
}
}
- private void removeUserStateLocked(final int userHandle) {
+ private void removeUserStateLILP(final int userHandle) {
mContext.getSystemService(StorageManager.class)
.deleteUserKey(userHandle);
// Cleanup package manager settings
mPm.cleanUpUserLILPw(this, userHandle);
// Remove this user from the list
- mUsers.remove(userHandle);
+ synchronized (mUsersLock) {
+ mUsers.remove(userHandle);
+ }
// Remove user file
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
userFile.delete();
// Update the user list
- writeUserListLocked();
- updateUserIdsLocked();
+ writeUserListLILP();
+ updateUserIds();
removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
}
@@ -1743,7 +1805,7 @@
}
synchronized (mPackagesLock) {
// Read the restrictions from XML
- return readApplicationRestrictionsLocked(packageName, userId);
+ return readApplicationRestrictionsLP(packageName, userId);
}
}
@@ -1756,7 +1818,7 @@
cleanAppRestrictionsForPackage(packageName, userId);
} else {
// Write the restrictions to XML
- writeApplicationRestrictionsLocked(packageName, restrictions, userId);
+ writeApplicationRestrictionsLP(packageName, restrictions, userId);
}
}
@@ -1804,16 +1866,15 @@
}
}
- private Bundle readApplicationRestrictionsLocked(String packageName,
- int userId) {
+ private Bundle readApplicationRestrictionsLP(String packageName, int userId) {
AtomicFile restrictionsFile =
new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
packageToRestrictionsFileName(packageName)));
- return readApplicationRestrictionsLocked(restrictionsFile);
+ return readApplicationRestrictionsLP(restrictionsFile);
}
@VisibleForTesting
- static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) {
+ static Bundle readApplicationRestrictionsLP(AtomicFile restrictionsFile) {
final Bundle restrictions = new Bundle();
final ArrayList<String> values = new ArrayList<>();
if (!restrictionsFile.getBaseFile().exists()) {
@@ -1896,17 +1957,16 @@
return childBundle;
}
- private void writeApplicationRestrictionsLocked(String packageName,
+ private void writeApplicationRestrictionsLP(String packageName,
Bundle restrictions, int userId) {
AtomicFile restrictionsFile = new AtomicFile(
new File(Environment.getUserSystemDirectory(userId),
packageToRestrictionsFileName(packageName)));
- writeApplicationRestrictionsLocked(restrictions, restrictionsFile);
+ writeApplicationRestrictionsLP(restrictions, restrictionsFile);
}
@VisibleForTesting
- static void writeApplicationRestrictionsLocked(Bundle restrictions,
- AtomicFile restrictionsFile) {
+ static void writeApplicationRestrictionsLP(Bundle restrictions, AtomicFile restrictionsFile) {
FileOutputStream fos = null;
try {
fos = restrictionsFile.startWrite();
@@ -1976,17 +2036,17 @@
@Override
public int getUserSerialNumber(int userHandle) {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
if (!exists(userHandle)) return -1;
- return getUserInfoLocked(userHandle).serialNumber;
+ return getUserInfoLU(userHandle).serialNumber;
}
}
@Override
public int getUserHandle(int userSerialNumber) {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
for (int userId : mUserIds) {
- UserInfo info = getUserInfoLocked(userId);
+ UserInfo info = getUserInfoLU(userId);
if (info != null && info.serialNumber == userSerialNumber) return userId;
}
// Not found
@@ -1998,13 +2058,13 @@
public long getUserCreationTime(int userHandle) {
int callingUserId = UserHandle.getCallingUserId();
UserInfo userInfo = null;
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
if (callingUserId == userHandle) {
- userInfo = getUserInfoLocked(userHandle);
+ userInfo = getUserInfoLU(userHandle);
} else {
- UserInfo parent = getProfileParentLocked(userHandle);
+ UserInfo parent = getProfileParentLU(userHandle);
if (parent != null && parent.id == callingUserId) {
- userInfo = getUserInfoLocked(userHandle);
+ userInfo = getUserInfoLU(userHandle);
}
}
}
@@ -2018,22 +2078,24 @@
/**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
- private void updateUserIdsLocked() {
+ private void updateUserIds() {
int num = 0;
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).partial) {
- num++;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ if (!mUsers.valueAt(i).partial) {
+ num++;
+ }
}
- }
- final int[] newUsers = new int[num];
- int n = 0;
- for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).partial) {
- newUsers[n++] = mUsers.keyAt(i);
+ final int[] newUsers = new int[num];
+ int n = 0;
+ for (int i = 0; i < userSize; i++) {
+ if (!mUsers.valueAt(i).partial) {
+ newUsers[n++] = mUsers.keyAt(i);
+ }
}
+ mUserIds = newUsers;
}
- mUserIds = newUsers;
}
/**
@@ -2042,7 +2104,7 @@
*/
public void onUserForeground(int userId) {
synchronized (mPackagesLock) {
- UserInfo user = mUsers.get(userId);
+ UserInfo user = getUserInfoNoChecks(userId);
long now = System.currentTimeMillis();
if (user == null || user.partial) {
Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
@@ -2050,7 +2112,7 @@
}
if (now > EPOCH_PLUS_30_YEARS) {
user.lastLoggedInTime = now;
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
}
}
@@ -2061,8 +2123,8 @@
* for data and battery stats collection, or unexpected cross-talk.
* @return
*/
- private int getNextAvailableIdLocked() {
- synchronized (mPackagesLock) {
+ private int getNextAvailableId() {
+ synchronized (mUsersLock) {
int i = MIN_USER_ID;
while (i < MAX_USER_ID) {
if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
@@ -2154,6 +2216,45 @@
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ }
+
+ int onShellCommand(Shell shell, String cmd) {
+ if (cmd == null) {
+ return shell.handleDefaultCommands(cmd);
+ }
+
+ final PrintWriter pw = shell.getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "list":
+ return runList(pw);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runList(PrintWriter pw) throws RemoteException {
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ final List<UserInfo> users = getUsers(false);
+ if (users == null) {
+ pw.println("Error: couldn't get users");
+ return 1;
+ } else {
+ pw.println("Users:");
+ for (int i = 0; i < users.size(); i++) {
+ String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
+ pw.println("\t" + users.get(i).toString() + running);
+ }
+ return 0;
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2168,38 +2269,49 @@
long now = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
synchronized (mPackagesLock) {
- pw.println("Users:");
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo user = mUsers.valueAt(i);
- if (user == null) continue;
- pw.print(" "); pw.print(user); pw.print(" serialNo="); pw.print(user.serialNumber);
- if (mRemovingUserIds.get(mUsers.keyAt(i))) pw.print(" <removing> ");
- if (user.partial) pw.print(" <partial>");
- pw.println();
- pw.print(" Created: ");
- if (user.creationTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - user.creationTime, sb);
- sb.append(" ago");
- pw.println(sb);
+ synchronized (mUsersLock) {
+ pw.println("Users:");
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ if (user == null) {
+ continue;
+ }
+ pw.print(" "); pw.print(user);
+ pw.print(" serialNo="); pw.print(user.serialNumber);
+ if (mRemovingUserIds.get(mUsers.keyAt(i))) {
+ pw.print(" <removing> ");
+ }
+ if (user.partial) {
+ pw.print(" <partial>");
+ }
+ pw.println();
+ pw.print(" Created: ");
+ if (user.creationTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.creationTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ pw.print(" Last logged in: ");
+ if (user.lastLoggedInTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ pw.println(" Restrictions:");
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mBaseUserRestrictions.get(user.id));
+ pw.println(" Effective restrictions:");
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
+ }
}
- pw.print(" Last logged in: ");
- if (user.lastLoggedInTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
- sb.append(" ago");
- pw.println(sb);
- }
- pw.println(" Restrictions:");
- UserRestrictionsUtils.dumpRestrictions(
- pw, " ", mBaseUserRestrictions.get(user.id));
- pw.println(" Effective restrictions:");
- UserRestrictionsUtils.dumpRestrictions(
- pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
}
pw.println();
pw.println("Guest restrictions:");
@@ -2216,9 +2328,9 @@
removeMessages(WRITE_USER_MSG, msg.obj);
synchronized (mPackagesLock) {
int userId = ((UserInfo) msg.obj).id;
- UserInfo userInfo = mUsers.get(userId);
+ UserInfo userInfo = getUserInfoNoChecks(userId);
if (userInfo != null) {
- writeUserLocked(userInfo);
+ writeUserLP(userInfo);
}
}
}
@@ -2242,14 +2354,14 @@
@Override
@GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsRL(int userId) {
- UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+ public void updateEffectiveUserRestrictionsLR(int userId) {
+ UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
}
@Override
@GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsForAllUsersRL() {
- UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+ public void updateEffectiveUserRestrictionsForAllUsersLR() {
+ UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
}
@Override
@@ -2264,17 +2376,35 @@
int userId, Bundle baseRestrictions) {
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
- invalidateEffectiveUserRestrictionsRL(userId);
+ invalidateEffectiveUserRestrictionsLR(userId);
}
+ final UserInfo userInfo = getUserInfoNoChecks(userId);
synchronized (mPackagesLock) {
- final UserInfo userInfo = mUsers.get(userId);
if (userInfo != null) {
- writeUserLocked(userInfo);
+ writeUserLP(userInfo);
} else {
Slog.w(LOG_TAG, "UserInfo not found for " + userId);
}
}
}
}
+
+ private class Shell extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("User manager (user) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" list");
+ pw.println(" Prints all users on the system.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 23e3b35..28df9f6 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,10 +18,19 @@
import com.google.android.collect.Sets;
-import com.android.internal.util.Preconditions;
-
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.IAudioService;
+import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Slog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -31,6 +40,8 @@
import java.util.Set;
public class UserRestrictionsUtils {
+ private static final String TAG = "UserRestrictionsUtils";
+
private UserRestrictionsUtils() {
}
@@ -115,6 +126,118 @@
}
}
+ /**
+ * Takes a new use restriction set and the previous set, and apply the restrictions that have
+ * changed.
+ */
+ public static void applyUserRestrictions(Context context, int userId,
+ @Nullable Bundle newRestrictions, @Nullable Bundle prevRestrictions) {
+ if (newRestrictions == null) {
+ newRestrictions = Bundle.EMPTY;
+ }
+ if (prevRestrictions == null) {
+ prevRestrictions = Bundle.EMPTY;
+ }
+ for (String key : USER_RESTRICTIONS) {
+ final boolean newValue = newRestrictions.getBoolean(key);
+ final boolean prevValue = prevRestrictions.getBoolean(key);
+
+ if (newValue != prevValue) {
+ applyUserRestriction(context, userId, key, newValue);
+ }
+ }
+ }
+
+ private static void applyUserRestriction(Context context, int userId, String key,
+ boolean newValue) {
+ // When certain restrictions are cleared, we don't update the system settings,
+ // because these settings are changeable on the Settings UI and we don't know the original
+ // value -- for example LOCATION_MODE might have been off already when the restriction was
+ // set, and in that case even if the restriction is lifted, changing it to ON would be
+ // wrong. So just don't do anything in such a case. If the user hopes to enable location
+ // later, they can do it on the Settings UI.
+
+ final ContentResolver cr = context.getContentResolver();
+ final long id = Binder.clearCallingIdentity();
+ try {
+ switch (key) {
+ case UserManager.DISALLOW_UNMUTE_MICROPHONE:
+ IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+ .setMicrophoneMute(newValue, context.getPackageName(), userId);
+ break;
+ case UserManager.DISALLOW_ADJUST_VOLUME:
+ IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+ .setMasterMute(newValue, 0, context.getPackageName(), userId);
+ break;
+ case UserManager.DISALLOW_CONFIG_WIFI:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure
+ .WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId);
+ }
+ break;
+ case UserManager.DISALLOW_SHARE_LOCATION:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure.LOCATION_MODE,
+ android.provider.Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ android.provider.Settings.Secure.putStringForUser(cr,
+ android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+ userId);
+ }
+ // Send out notifications as some clients may want to reread the
+ // value which actually changed due to a restriction having been
+ // applied.
+ final String property =
+ android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION;
+ long version = SystemProperties.getLong(property, 0) + 1;
+ SystemProperties.set(property, Long.toString(version));
+
+ final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+ final Uri url = Uri.withAppendedPath(
+ android.provider.Settings.Secure.CONTENT_URI, name);
+ context.getContentResolver().notifyChange(url, null, true, userId);
+
+ break;
+ case UserManager.DISALLOW_DEBUGGING_FEATURES:
+ if (newValue) {
+ // Only disable adb if changing for system user, since it is global
+ // TODO: should this be admin user?
+ if (userId == UserHandle.USER_SYSTEM) {
+ android.provider.Settings.Global.putStringForUser(cr,
+ android.provider.Settings.Global.ADB_ENABLED, "0",
+ userId);
+ }
+ }
+ break;
+ case UserManager.ENSURE_VERIFY_APPS:
+ if (newValue) {
+ android.provider.Settings.Global.putStringForUser(
+ context.getContentResolver(),
+ android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+ userId);
+ android.provider.Settings.Global.putStringForUser(
+ context.getContentResolver(),
+ android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+ userId);
+ }
+ break;
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+ userId);
+ }
+ break;
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Failed to talk to AudioService.", re);
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+
public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
boolean noneSet = true;
if (restrictions != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b38b9ce..209751e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,8 +16,8 @@
package com.android.server.policy;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index d4c5f87..ac79b36 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -40,6 +40,8 @@
private boolean mRelroReady32Bit = false;
private boolean mRelroReady64Bit = false;
+ private String oldWebViewPackageName = null;
+
private BroadcastReceiver mWebViewUpdatedReceiver;
public WebViewUpdateService(Context context) {
@@ -51,9 +53,22 @@
mWebViewUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
- if (webviewPackage.equals(intent.getDataString())) {
- onWebViewUpdateInstalled();
+
+ for (String packageName : WebViewFactory.getWebViewPackageNames()) {
+ String webviewPackage = "package:" + packageName;
+
+ if (webviewPackage.equals(intent.getDataString())) {
+ String usedPackageName =
+ WebViewFactory.findPreferredWebViewPackage().packageName;
+ // Only trigger update actions if the updated package is the one that
+ // will be used, or the one that was in use before the update.
+ if (packageName.equals(usedPackageName) ||
+ packageName.equals(oldWebViewPackageName)) {
+ onWebViewUpdateInstalled();
+ oldWebViewPackageName = usedPackageName;
+ }
+ return;
+ }
}
}
};
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c5bd3a7..9143097 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -102,6 +102,7 @@
// Set to true when the token has been removed from the window mgr.
boolean removed;
+ boolean appDied;
// Information about an application starting window if displayed.
StartingData startingData;
WindowState startingWindow;
@@ -365,6 +366,26 @@
windows.clear();
}
+ void removeAllDeadWindows() {
+ for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
+ // removeWindowLocked at bottom of loop may remove multiple entries from
+ // allAppWindows if the window to be removed has child windows. It also may
+ // not remove any windows from allAppWindows at all if win is exiting and
+ // currently animating away. This ensures that winNdx is monotonically decreasing
+ // and never beyond allAppWindows bounds.
+ winNdx = Math.min(winNdx - 1, allAppWindows.size() - 1)) {
+ WindowState win = allAppWindows.get(winNdx);
+ if (win.mAppDied) {
+ if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) {
+ Slog.w(WindowManagerService.TAG, "removeAllDeadWindows: " + win);
+ }
+ // Set mDestroying, we don't want any animation or delayed removal here.
+ win.mDestroying = true;
+ service.removeWindowLocked(win);
+ }
+ }
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DimBehindController.java b/services/core/java/com/android/server/wm/DimLayerController.java
similarity index 78%
rename from services/core/java/com/android/server/wm/DimBehindController.java
rename to services/core/java/com/android/server/wm/DimLayerController.java
index 8870dd1..f9aca00 100644
--- a/services/core/java/com/android/server/wm/DimBehindController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -1,6 +1,7 @@
package com.android.server.wm;
import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -11,32 +12,38 @@
/**
* Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}.
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
+ * as well as other use cases (such as dimming above a dead window).
*/
-class DimBehindController {
- private static final String TAG = "DimBehindController";
+class DimLayerController {
+ private static final String TAG = "DimLayerController";
/** Amount of time in milliseconds to animate the dim surface from one value to another,
* when no window animation is driving it. */
private static final int DEFAULT_DIM_DURATION = 200;
- // Shared dim layer for fullscreen users. {@link DimBehindState#dimLayer} will point to this
+ /**
+ * The default amount of dim applied over a dead window
+ */
+ private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
+ // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
// instead of creating a new object per fullscreen task on a display.
private DimLayer mSharedFullScreenDimLayer;
- private ArrayMap<DimLayer.DimLayerUser, DimBehindState> mState = new ArrayMap<>();
+ private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
private DisplayContent mDisplayContent;
private Rect mTmpBounds = new Rect();
- DimBehindController(DisplayContent displayContent) {
+ DimLayerController(DisplayContent displayContent) {
mDisplayContent = displayContent;
}
/** Updates the dim layer bounds, recreating it if needed. */
void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
- DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+ DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
final boolean previousFullscreen = state.dimLayer != null
&& state.dimLayer == mSharedFullScreenDimLayer;
DimLayer newDimLayer;
@@ -60,7 +67,7 @@
newDimLayer.setBounds(mTmpBounds);
mSharedFullScreenDimLayer = newDimLayer;
} else if (state.dimLayer != null) {
- state.dimLayer. destroySurface();
+ state.dimLayer.destroySurface();
}
} else {
newDimLayer = (state.dimLayer == null || previousFullscreen)
@@ -72,19 +79,21 @@
state.dimLayer = newDimLayer;
}
- private DimBehindState getOrCreateDimBehindState(DimLayer.DimLayerUser dimLayerUser) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "getDimBehindState, dimLayerUser="
+ private DimLayerState getOrCreateDimLayerState(
+ DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
+ if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
+ dimLayerUser.toShortString());
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
if (state == null) {
- state = new DimBehindState();
+ state = new DimLayerState();
mState.put(dimLayerUser, state);
}
+ state.dimAbove = aboveApp;
return state;
}
private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
if (state == null) {
if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
+ dimLayerUser.toShortString());
@@ -95,7 +104,7 @@
boolean isDimming() {
for (int i = mState.size() - 1; i >= 0; i--) {
- DimBehindState state = mState.valueAt(i);
+ DimLayerState state = mState.valueAt(i);
if (state.dimLayer != null && state.dimLayer.isDimming()) {
return true;
}
@@ -110,15 +119,15 @@
}
private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
return state != null && state.continueDimming;
}
void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
- WindowStateAnimator newWinAnimator) {
+ WindowStateAnimator newWinAnimator, boolean aboveApp) {
// Only set dim params on the highest dimmed layer.
// Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
- DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+ DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " newWinAnimator=" + newWinAnimator
@@ -145,7 +154,7 @@
private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
// No need to check if state is null, we know the key has a value.
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " state.continueDimming=" + state.continueDimming
@@ -188,7 +197,7 @@
}
private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " state.animator=" + state.animator
@@ -199,8 +208,13 @@
dimLayer = state.dimLayer.getLayer();
dimAmount = 0;
} else {
- dimLayer = state.animator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
- dimAmount = state.animator.mWin.mAttrs.dimAmount;
+ if (state.dimAbove) {
+ dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
+ dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
+ } else {
+ dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
+ dimAmount = state.animator.mWin.mAttrs.dimAmount;
+ }
}
final float targetAlpha = state.dimLayer.getTargetAlpha();
if (targetAlpha != dimAmount) {
@@ -211,7 +225,7 @@
? state.animator.mAnimation.computeDurationHint()
: DEFAULT_DIM_DURATION;
if (targetAlpha > dimAmount) {
- duration = getDimBehindFadeDuration(duration);
+ duration = getDimLayerFadeDuration(duration);
}
state.dimLayer.show(dimLayer, dimAmount, duration);
}
@@ -230,11 +244,11 @@
}
boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
- DimBehindState state = mState.get(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
}
- private long getDimBehindFadeDuration(long duration) {
+ private long getDimLayerFadeDuration(long duration) {
TypedValue tv = new TypedValue();
mDisplayContent.mService.mContext.getResources().getValue(
com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
@@ -248,7 +262,7 @@
void close() {
for (int i = mState.size() - 1; i >= 0; i--) {
- DimBehindState state = mState.valueAt(i);
+ DimLayerState state = mState.valueAt(i);
state.dimLayer.destroySurface();
}
mState.clear();
@@ -256,10 +270,23 @@
}
void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
- mState.remove(dimLayerUser);
+ DimLayerState state = mState.get(dimLayerUser);
+ if (state != null) {
+ state.dimLayer.destroySurface();
+ mState.remove(dimLayerUser);
+ }
}
void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+ applyDim(dimLayerUser, animator, false /* aboveApp */);
+ }
+
+ void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+ applyDim(dimLayerUser, animator, true /* aboveApp */);
+ }
+
+ private void applyDim(
+ DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
if (dimLayerUser == null) {
Slog.e(TAG, "Trying to apply dim layer for: " + this
+ ", but no dim layer user found.");
@@ -269,26 +296,27 @@
setContinueDimming(dimLayerUser);
if (!isDimming(dimLayerUser, animator)) {
if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
- startDimmingIfNeeded(dimLayerUser, animator);
+ startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
}
}
}
- private static class DimBehindState {
- // The particular window with FLAG_DIM_BEHIND set. If null, hide dimLayer.
+ private static class DimLayerState {
+ // The particular window requesting a dim layer. If null, hide dimLayer.
WindowStateAnimator animator;
// Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
// end then stop any dimming.
boolean continueDimming;
DimLayer dimLayer;
+ boolean dimAbove;
}
void dump(String prefix, PrintWriter pw) {
- pw.println(prefix + "DimBehindController");
+ pw.println(prefix + "DimLayerController");
for (int i = 0, n = mState.size(); i < n; i++) {
pw.println(prefix + " " + mState.keyAt(i).toShortString());
pw.print(prefix + " ");
- DimBehindState state = mState.valueAt(i);
+ DimLayerState state = mState.valueAt(i);
pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
state.dimLayer));
pw.print(", animator=" + state.animator);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fab8ee5..53f8bbd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,14 +16,15 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import android.app.ActivityManager.StackId;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.DisplayMetrics;
@@ -115,7 +116,7 @@
final DockedStackDividerController mDividerControllerLocked;
- final DimBehindController mDimBehindController;
+ final DimLayerController mDimLayerController;
/**
* @param display May not be null.
@@ -130,7 +131,7 @@
mService = service;
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
- mDimBehindController = new DimBehindController(this);
+ mDimLayerController = new DimLayerController(this);
}
int getDisplayId() {
@@ -270,7 +271,7 @@
}
void detachStack(TaskStack stack) {
- mDimBehindController.removeDimLayerUser(stack);
+ mDimLayerController.removeDimLayerUser(stack);
mStacks.remove(stack);
}
@@ -311,7 +312,7 @@
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
TaskStack stack = mStacks.get(stackNdx);
- if (!stack.allowTaskResize()) {
+ if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
break;
}
final ArrayList<Task> tasks = stack.getTasks();
@@ -414,23 +415,23 @@
}
boolean animateDimLayers() {
- return mDimBehindController.animateDimLayers();
+ return mDimLayerController.animateDimLayers();
}
void resetDimming() {
- mDimBehindController.resetDimming();
+ mDimLayerController.resetDimming();
}
boolean isDimming() {
- return mDimBehindController.isDimming();
+ return mDimLayerController.isDimming();
}
void stopDimmingIfNeeded() {
- mDimBehindController.stopDimmingIfNeeded();
+ mDimLayerController.stopDimmingIfNeeded();
}
void close() {
- mDimBehindController.close();
+ mDimLayerController.close();
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
mStacks.get(stackNdx).close();
}
@@ -577,7 +578,7 @@
}
}
pw.println();
- mDimBehindController.dump(prefix + " ", pw);
+ mDimLayerController.dump(prefix + " ", pw);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 04cba81..eafc3c6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f62bc1..c47c377 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -187,6 +187,12 @@
mService.removeWindow(this, window);
}
+ @Override
+ public void repositionChild(IWindow window, int x, int y, long deferTransactionUntilFrame,
+ Rect outFrame) {
+ mService.repositionChild(this, window, x, y, deferTransactionUntilFrame, outFrame);
+ }
+
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ad44196..5864b25 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -16,22 +16,21 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
-
+import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -117,7 +116,7 @@
mDeferRemoval = false;
DisplayContent content = getDisplayContent();
if (content != null) {
- content.mDimBehindController.removeDimLayerUser(this);
+ content.mDimLayerController.removeDimLayerUser(this);
}
mStack.removeTask(this);
mService.mTaskIdToTask.delete(mTaskId);
@@ -221,7 +220,7 @@
mBounds.set(bounds);
mRotation = rotation;
if (displayContent != null) {
- displayContent.mDimBehindController.updateDimLayer(this);
+ displayContent.mDimLayerController.updateDimLayer(this);
}
mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
return boundsChange;
@@ -245,7 +244,7 @@
private boolean useCurrentBounds() {
final DisplayContent displayContent = mStack.getDisplayContent();
if (mFullscreen
- || mStack.allowTaskResize()
+ || !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
|| displayContent == null
|| displayContent.getDockedStackLocked() != null) {
return true;
@@ -320,6 +319,15 @@
}
}
+ /**
+ * Cancels any running app transitions associated with the task.
+ */
+ void cancelTaskWindowTransition() {
+ for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+ mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+ }
+ }
+
boolean showForAllUsers() {
final int tokensCount = mAppTokens.size();
return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 227b3f0..aae3bd2 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -18,7 +18,7 @@
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index df664bd..f72384c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -17,17 +17,16 @@
package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
import static com.android.server.wm.WindowManagerService.TAG;
import android.annotation.IntDef;
+import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
@@ -122,12 +121,6 @@
}
}
- boolean allowTaskResize() {
- return mStackId == FREEFORM_WORKSPACE_STACK_ID
- || mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID;
- }
-
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -183,7 +176,7 @@
}
if (mDisplayContent != null) {
- mDisplayContent.mDimBehindController.updateDimLayer(this);
+ mDisplayContent.mDimLayerController.updateDimLayer(this);
mAnimationBackgroundSurface.setBounds(bounds);
}
@@ -203,8 +196,7 @@
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
if (mFullscreen
- || mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID
+ || !StackId.isResizeableByDockedStack(mStackId)
|| mDisplayContent == null
|| mDisplayContent.getDockedStackLocked() != null) {
return true;
@@ -396,9 +388,9 @@
Rect bounds = null;
final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
- if (mStackId == DOCKED_STACK_ID || (dockedStack != null && mStackId != PINNED_STACK_ID
- && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
- // The existence of a docked stack affects the size of any static stack created since
+ if (mStackId == DOCKED_STACK_ID
+ || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId))) {
+ // The existence of a docked stack affects the size of other static stack created since
// the docked stack occupies a dedicated region on screen.
bounds = new Rect();
displayContent.getLogicalDisplayRect(mTmpRect);
@@ -424,10 +416,7 @@
}
void getStackDockedModeBoundsLocked(Rect outBounds) {
- if (mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID
- || mStackId > LAST_STATIC_STACK_ID
- || mDisplayContent == null) {
+ if (!StackId.isResizeableByDockedStack(mStackId) || mDisplayContent == null) {
outBounds.set(mBounds);
return;
}
@@ -537,9 +526,7 @@
for (int i = 0; i < count; i++) {
final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
final int otherStackId = otherStack.mStackId;
- if (otherStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID
- && otherStackId >= FIRST_STATIC_STACK_ID
- && otherStackId <= LAST_STATIC_STACK_ID) {
+ if (StackId.isResizeableByDockedStack(otherStackId)) {
mService.mH.sendMessage(
mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
1 /*allowResizeInDockedMode*/, bounds));
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7f2f2cd..4a9d8cb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 75680a0..d5304c2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -1924,6 +1924,11 @@
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
+ if (atoken != null && atoken.appDied) {
+ Slog.d(TAG, "App is now revived: " + atoken);
+ atoken.appDied = false;
+ }
+
mPolicy.adjustWindowParamsLw(win.mAttrs);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
@@ -1935,11 +1940,7 @@
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
- String name = win.makeInputChannelName();
- InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- win.setInputChannel(inputChannels[0]);
- inputChannels[1].transferTo(outInputChannel);
- mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
+ win.openInputChannel(outInputChannel);
}
// From now on, no exceptions or errors allowed!
@@ -2183,13 +2184,31 @@
+ "added");
win.mExiting = true;
appToken.mReplacingRemoveRequested = true;
+ Binder.restoreCallingIdentity(origId);
return;
}
// If we are not currently running the exit animation, we
// need to see about starting one.
wasVisible = win.isWinVisibleLw();
- if (wasVisible) {
+ if (wasVisible && appToken != null && appToken.appDied) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG,
+ "Not removing " + win + " because app died while it's visible");
+
+ win.mAppDied = true;
+ win.setDisplayLayoutNeeded();
+ mWindowPlacerLocked.performSurfacePlacement();
+
+ // Set up a replacement input channel since the app is now dead.
+ // We need to catch tapping on the dead window to restart the app.
+ win.openInputChannel(null);
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
+
+ if (wasVisible) {
final int transit = (!startingWindow)
? WindowManagerPolicy.TRANSIT_EXIT
: WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
@@ -2240,10 +2259,6 @@
}
void removeWindowInnerLocked(WindowState win) {
- removeWindowInnerLocked(win, true);
- }
-
- private void removeWindowInnerLocked(WindowState win, boolean performLayout) {
if (win.mRemoved) {
// Nothing to do.
return;
@@ -2338,9 +2353,7 @@
if (!mWindowPlacerLocked.isInLayout()) {
assignLayersLocked(windows);
win.setDisplayLayoutNeeded();
- if (performLayout) {
- mWindowPlacerLocked.performSurfacePlacement();
- }
+ mWindowPlacerLocked.performSurfacePlacement();
if (win.mAppToken != null) {
win.mAppToken.updateReportedVisibilityLocked();
}
@@ -2468,6 +2481,54 @@
}
}
+ void repositionChild(Session session, IWindow client,
+ int x, int y, long deferTransactionUntilFrame, Rect outFrame) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
+ long origId = Binder.clearCallingIdentity();
+
+ try {
+ synchronized(mWindowMap) {
+ WindowState win = windowForClientLocked(session, client, false);
+ if (win == null) {
+ return;
+ }
+ if (win.mAttachedWindow == null) {
+ throw new IllegalArgumentException(
+ "repositionChild called but window is not"
+ + "attached to a parent win=" + win);
+ }
+
+ win.mFrame.left = x;
+ win.mFrame.top = y;
+
+ win.mWinAnimator.computeShownFrameLocked();
+
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG, ">>> OPEN TRANSACTION repositionChild");
+ }
+
+ SurfaceControl.openTransaction();
+
+ if (deferTransactionUntilFrame > 0) {
+ win.mWinAnimator.mSurfaceControl.deferTransactionUntil(
+ win.mAttachedWindow.mWinAnimator.mSurfaceControl.getHandle(),
+ deferTransactionUntilFrame);
+ }
+ win.mWinAnimator.setSurfaceBoundariesLocked(false);
+
+ SurfaceControl.closeTransaction();
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG, "<<< CLOSE TRANSACTION repositionChild");
+ }
+
+ outFrame = win.mCompatFrame;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
@@ -4070,6 +4131,14 @@
wtoken.waitingToShow = false;
wtoken.hiddenRequested = !visible;
+ if (!visible && wtoken.appDied) {
+ // This app is dead while it was visible, we kept its dead window on screen.
+ // Now that the app is going invisible, we can remove it. It will be restarted
+ // if made visible again.
+ wtoken.appDied = false;
+ wtoken.removeAllWindows();
+ }
+
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
@@ -4607,6 +4676,16 @@
}
}
+ @Override
+ public void cancelTaskWindowTransition(int taskId) {
+ synchronized (mWindowMap) {
+ Task task = mTaskIdToTask.get(taskId);
+ if (task != null) {
+ task.cancelTaskWindowTransition();
+ }
+ }
+ }
+
public void addTask(int taskId, int stackId, boolean toTop) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG, "addTask: adding taskId=" + taskId
@@ -8490,7 +8569,7 @@
final DimLayer.DimLayerUser dimLayerUser = w.getDimLayerUser();
final DisplayContent displayContent = w.getDisplayContent();
if (layerChanged && dimLayerUser != null && displayContent != null &&
- displayContent.mDimBehindController.isDimming(dimLayerUser, winAnimator)) {
+ displayContent.mDimLayerController.isDimming(dimLayerUser, winAnimator)) {
// Force an animation pass just to update the mDimLayer layer.
scheduleAnimationLocked();
}
@@ -8609,6 +8688,13 @@
+ " dragResizingChanged=" + dragResizingChanged);
}
+ // If it's a dead window left on screen, and the configuration changed,
+ // there is nothing we can do about it. Remove the window now.
+ if (w.mAppToken != null && w.mAppDied) {
+ w.mAppToken.removeAllDeadWindows();
+ return;
+ }
+
w.mLastOverscanInsets.set(w.mOverscanInsets);
w.mLastContentInsets.set(w.mContentInsets);
w.mLastVisibleInsets.set(w.mVisibleInsets);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bc329e..984a353 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -340,6 +340,12 @@
boolean mRemoveOnExit;
/**
+ * Whether the app died while it was visible, if true we might need
+ * to continue to show it until it's restarted.
+ */
+ boolean mAppDied;
+
+ /**
* Set when the orientation is changing and this window has not yet
* been updated for the new orientation.
*/
@@ -362,6 +368,7 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
+ InputChannel mClientChannel;
// Used to improve performance of toString()
String mStringNameCache;
@@ -1274,16 +1281,28 @@
mConfigHasChanged = false;
}
- void setInputChannel(InputChannel inputChannel) {
+ void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
-
- mInputChannel = inputChannel;
- mInputWindowHandle.inputChannel = inputChannel;
+ String name = makeInputChannelName();
+ InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+ mInputChannel = inputChannels[0];
+ mClientChannel = inputChannels[1];
+ mInputWindowHandle.inputChannel = inputChannels[0];
+ if (outInputChannel != null) {
+ mClientChannel.transferTo(outInputChannel);
+ mClientChannel.dispose();
+ mClientChannel = null;
+ }
+ mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
void disposeInputChannel() {
+ if (mClientChannel != null) {
+ mClientChannel.dispose();
+ mClientChannel = null;
+ }
if (mInputChannel != null) {
mService.mInputManager.unregisterInputChannel(mInputChannel);
@@ -1294,10 +1313,13 @@
mInputWindowHandle.inputChannel = null;
}
- void handleFlagDimBehind() {
- if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && mDisplayContent != null && !mExiting
- && isDisplayedLw()) {
- mDisplayContent.mDimBehindController.applyDimBehind(getDimLayerUser(), mWinAnimator);
+ void applyDimLayerIfNeeded() {
+ if (!mExiting && mAppDied) {
+ // If app died visible, apply a dim over the window to indicate that it's inactive
+ mDisplayContent.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
+ } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+ && mDisplayContent != null && !mExiting && isDisplayedLw()) {
+ mDisplayContent.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
}
}
@@ -1375,6 +1397,9 @@
WindowState win = mService.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
+ if (win.mAppToken != null && !win.mAppToken.clientHidden) {
+ win.mAppToken.appDied = true;
+ }
mService.removeWindowLocked(win);
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
@@ -1574,7 +1599,7 @@
public boolean isDimming() {
final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
return dimLayerUser != null && mDisplayContent != null &&
- mDisplayContent.mDimBehindController.isDimming(dimLayerUser, mWinAnimator);
+ mDisplayContent.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
}
public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
@@ -1833,7 +1858,8 @@
pw.print(prefix); pw.print("mToken="); pw.println(mToken);
pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken);
if (mAppToken != null) {
- pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+ pw.print(prefix); pw.print("mAppToken="); pw.print(mAppToken);
+ pw.print(" mAppDied=");pw.println(mAppDied);
}
if (mTargetAppToken != null) {
pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken);
@@ -1999,4 +2025,13 @@
}
return mStringNameCache;
}
+
+ void transformFromScreenToSurfaceSpace(Rect rect) {
+ if (mHScale >= 0) {
+ rect.right = rect.left + (int)((rect.right - rect.left) / mHScale);
+ }
+ if (mVScale >= 0) {
+ rect.bottom = rect.top + (int)((rect.bottom - rect.top) / mVScale);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index aa242f1..e9be2dd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1223,14 +1223,6 @@
mShownAlpha *= appTransformation.getAlpha();
if (appTransformation.hasClipRect()) {
mClipRect.set(appTransformation.getClipRect());
- if (mWin.mHScale > 0) {
- mClipRect.left /= mWin.mHScale;
- mClipRect.right /= mWin.mHScale;
- }
- if (mWin.mVScale > 0) {
- mClipRect.top /= mWin.mVScale;
- mClipRect.bottom /= mWin.mVScale;
- }
mHasClipRect = true;
}
}
@@ -1350,11 +1342,7 @@
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Need to recompute a new system decor rect each time.
- if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
- // Currently can't do this cropping for scaled windows. We'll
- // just keep the crop rect the same as the source surface.
- w.mSystemDecorRect.set(0, 0, w.mRequestedWidth, w.mRequestedHeight);
- } else if (!w.isDefaultDisplay()) {
+ if (!w.isDefaultDisplay()) {
// On a different display there is no system decor. Crop the window
// by the screen boundaries.
w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
@@ -1407,9 +1395,13 @@
clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
// We don't want to clip to stack bounds windows that are currently doing entrance
// animation for docked window, otherwise the animating window will be suddenly cut off.
+
if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
adjustCropToStackBounds(w, clipRect);
}
+
+ w.transformFromScreenToSurfaceSpace(clipRect);
+
if (!clipRect.equals(mLastClipRect)) {
mLastClipRect.set(clipRect);
try {
@@ -1434,12 +1426,13 @@
private void adjustCropToStackBounds(WindowState w, Rect clipRect) {
final AppWindowToken appToken = w.mAppToken;
+ final Task task = w.getTask();
// We don't apply the the stack bounds to the window that is being replaced, because it was
// living in a different stack. If we suddenly crop it to the new stack bounds, it might
// get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
// gets removed. The window that will replace it will abide them.
- if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
- TaskStack stack = w.getTask().mStack;
+ if (task != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
+ TaskStack stack = task.mStack;
stack.getBounds(mTmpStackBounds);
// When we resize we use the big surface approach, which means we can't trust the
// window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
@@ -1551,7 +1544,7 @@
mDsDy * w.mHScale, mDtDy * w.mVScale);
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- w.handleFlagDimBehind();
+ w.applyDimLayerIfNeeded();
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
// as running out of memory), don't take down the
@@ -1867,6 +1860,9 @@
if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
mWin.mAppToken.firstWindowDrawn = true;
+ // We now have a good window to show, remove dead placeholders
+ mWin.mAppToken.removeAllDeadWindows();
+
if (mWin.mAppToken.startingData != null) {
if (WindowManagerService.DEBUG_STARTING_WINDOW ||
WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aca0f5b..eef8e17 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -670,7 +670,7 @@
handleNotObscuredLocked(w, innerDw, innerDh);
}
- w.handleFlagDimBehind();
+ w.applyDimLayerIfNeeded();
if (isDefaultDisplay && obscuredChanged
&& mWallpaperControllerLocked.isWallpaperTarget(w) && w.isVisibleLw()) {
@@ -781,7 +781,7 @@
+ " a=" + winAnimator.mAnimating);
}
}
- if (w != atoken.startingWindow) {
+ if (w != atoken.startingWindow && !w.mAppDied) {
if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
atoken.numInterestingWindows++;
if (w.isDrawnLw()) {
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
index 06b9bc3..e12a016 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -43,12 +43,26 @@
jint card, jint device)
{
char path[100];
+ int fd;
+ const int kMaxRetries = 10;
+ const int kSleepMicroseconds = 2000;
snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
- int fd = open(path, O_RDWR);
- if (fd < 0) {
- ALOGE("could not open %s", path);
- return 0;
+ // This control device may not have been created yet. So we should
+ // try to open it several times to prevent intermittent failure
+ // from a race condition.
+ int retryCounter = 0;
+ while ((fd = open(path, O_RDWR)) < 0) {
+ if (++retryCounter > kMaxRetries) {
+ ALOGE("timed out after %d tries, could not open %s", retryCounter, path);
+ return 0;
+ } else {
+ ALOGW("attempt #%d, could not open %s", retryCounter, path);
+ // Increase the sleep interval each time.
+ // 10 retries will total 2 * sum(1..10) = 110 milliseconds.
+ // Typically the device should be ready in 5-10 milliseconds.
+ usleep(kSleepMicroseconds * retryCounter);
+ }
}
struct snd_rawmidi_info info;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b4c8f96..aea2ecf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1016,6 +1016,7 @@
}
}
+ // DO NOT call it while taking the "this" lock, which could cause a dead lock.
private void handlePackagesChanged(String packageName, int userHandle) {
boolean removed = false;
if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1042,7 +1043,6 @@
}
if (removed) {
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
saveSettingsLocked(policy.mUserHandle);
}
@@ -1061,6 +1061,13 @@
}
}
}
+ if (removed) {
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (DevicePolicyManagerService.this) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+ }
+ }
+ }
}
/**
@@ -1682,7 +1689,7 @@
}
}
- void removeActiveAdminLocked(final ComponentName adminReceiver, int userHandle) {
+ void removeActiveAdminLocked(final ComponentName adminReceiver, final int userHandle) {
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin != null) {
synchronized (this) {
@@ -1701,7 +1708,6 @@
policy.mAdminList.remove(admin);
policy.mAdminMap.remove(adminReceiver);
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
if (doProxyCleanup) {
resetGlobalProxyLocked(getUserData(userHandle));
}
@@ -1709,6 +1715,12 @@
updateMaximumTimeToLockLocked(policy);
policy.mRemovingAdmins.remove(adminReceiver);
}
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (DevicePolicyManagerService.this) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(
+ userHandle);
+ }
+ }
}
});
}
@@ -2022,7 +2034,6 @@
}
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
updateMaximumTimeToLockLocked(policy);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
if (policy.mStatusBarDisabled) {
@@ -2089,31 +2100,6 @@
}
}
- /**
- * Pushes down policy information to the system for any policies related to general device
- * capabilities that need to be enforced by lower level services (e.g. Camera services).
- */
- void syncDeviceCapabilitiesLocked(DevicePolicyData policy) {
- // Ensure the status of the camera is synced down to the system. Interested native services
- // should monitor this value and act accordingly.
- String cameraPropertyForUser = SYSTEM_PROP_DISABLE_CAMERA_PREFIX + policy.mUserHandle;
- boolean systemState = mInjector.systemPropertiesGetBoolean(cameraPropertyForUser, false);
- boolean cameraDisabled = getCameraDisabled(null, policy.mUserHandle);
- if (cameraDisabled != systemState) {
- long token = mInjector.binderClearCallingIdentity();
- try {
- String value = cameraDisabled ? "1" : "0";
- if (VERBOSE_LOG) {
- Slog.v(LOG_TAG, "Change in camera state ["
- + cameraPropertyForUser + "] = " + value);
- }
- mInjector.systemPropertiesSet(cameraPropertyForUser, value);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
- }
- }
-
@VisibleForTesting
void systemReady(int phase) {
if (!mHasFeature) {
@@ -4329,13 +4315,6 @@
}
/**
- * The system property used to share the state of the camera. The native camera service
- * is expected to read this property and act accordingly. The userId should be appended
- * to this key.
- */
- public static final String SYSTEM_PROP_DISABLE_CAMERA_PREFIX = "sys.secpolicy.camera.off_";
-
- /**
* Disables all device cameras according to the specified admin.
*/
@Override
@@ -4352,7 +4331,16 @@
ap.disableCamera = disabled;
saveSettingsLocked(userHandle);
}
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
+ }
+ // Tell the user manager that the restrictions have changed.
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (this) {
+ if (isDeviceOwner(who)) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
+ } else {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+ }
+ }
}
}
@@ -4370,7 +4358,13 @@
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
return (admin != null) ? admin.disableCamera : false;
}
+ // First, see if DO has set it. If so, it's device-wide.
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.disableCamera) {
+ return true;
+ }
+ // Then check each device admin on the user.
DevicePolicyData policy = getUserData(userHandle);
// Determine whether or not the device camera is disabled for any active admins.
final int N = policy.mAdminList.size();
@@ -4404,7 +4398,6 @@
ap.disabledKeyguardFeatures = which;
saveSettingsLocked(userHandle);
}
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
}
}
@@ -4657,7 +4650,7 @@
0 /* flagValues */, userHandle.getIdentifier());
// TODO This will not revert audio mute restrictions if they were set. b/24981972
synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
}
} catch (RemoteException re) {
} finally {
@@ -5036,7 +5029,6 @@
DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args));
saveSettingsLocked(userHandle);
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
}
}
@@ -5602,6 +5594,7 @@
}
}
+ // DO NOT call it while taking the "this" lock, which could cause a dead lock.
@Override
public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5612,7 +5605,7 @@
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- boolean isDeviceOwner = isDeviceOwner(who);
+ final boolean isDeviceOwner = isDeviceOwner(who);
if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
&& DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
throw new SecurityException(
@@ -5624,9 +5617,6 @@
final long id = mInjector.binderClearCallingIdentity();
try {
- // Original value.
- final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
-
// Save the restriction to ActiveAdmin.
// TODO When DO sets a restriction, it'll always be treated as device-wide.
// If there'll be a policy that can be set by both, we'll need scoping support,
@@ -5635,85 +5625,12 @@
activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
saveSettingsLocked(userHandle);
- // Tell UserManager the new value. Note this needs to be done before calling
- // into AudioService, because AS will check AppOps that'll be updated by UM.
+ // Tell UserManager the new value.
if (isDeviceOwner) {
- mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+ mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
} else {
- mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
}
-
- // New value.
- final boolean enabled = mUserManager.hasUserRestriction(key, user);
-
- // TODO The rest of the code should move to UserManagerService.
-
- if (enabled && !alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
- userHandle);
- } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF,
- userHandle);
- mInjector.settingsSecurePutStringForUser(
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
- userHandle);
- } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
- // Only disable adb if changing for system user, since it is global
- // TODO: should this be admin user?
- if (userHandle == UserHandle.USER_SYSTEM) {
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.ADB_ENABLED, "0", userHandle);
- }
- } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
- userHandle);
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
- userHandle);
- } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
- userHandle);
- }
- }
-
- if (enabled != alreadyRestricted) {
- if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- // Send out notifications however as some clients may want to reread the
- // value which actually changed due to a restriction having been
- // applied.
- final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
- long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
- mInjector.systemPropertiesSet(property, Long.toString(version));
-
- final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
- Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
- mContext.getContentResolver().notifyChange(url, null, true, userHandle);
- }
- }
- if (!enabled && alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(false, mContext.getPackageName(),
- userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
- }
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -6463,8 +6380,10 @@
deviceOwner == null ? null : deviceOwner.userRestrictions;
final Bundle profileOwnerRestrictions =
profileOwner == null ? null : profileOwner.userRestrictions;
+ final boolean cameraDisabled = getCameraDisabled(null, userId);
- if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+ if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
+ && !cameraDisabled) {
// No restrictions to merge.
return inBundle;
}
@@ -6473,6 +6392,11 @@
UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
+ // Also merge in the camera restriction.
+ if (cameraDisabled) {
+ composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ }
+
return composed;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index eb7eb15..9e70138 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -46,11 +46,11 @@
public void testWriteReadApplicationRestrictions() throws IOException {
AtomicFile atomicFile = new AtomicFile(restrictionsFile);
Bundle bundle = createBundle();
- UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile);
+ UserManagerService.writeApplicationRestrictionsLP(bundle, atomicFile);
assertTrue(atomicFile.getBaseFile().exists());
String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
System.out.println("restrictionsFile: " + s);
- bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile);
+ bundle = UserManagerService.readApplicationRestrictionsLP(atomicFile);
System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
assertBundle(bundle);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 13e600e..013154b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -385,9 +385,11 @@
timeNow);
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
- final String packageName = packages.get(p).packageName;
- final boolean isIdle = isAppIdleFiltered(packageName, userId, service, timeNow,
- screenOnTime);
+ final PackageInfo pi = packages.get(p);
+ final String packageName = pi.packageName;
+ final boolean isIdle = isAppIdleFiltered(packageName,
+ UserHandle.getAppId(pi.applicationInfo.uid),
+ userId, service, timeNow, screenOnTime);
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
userId, isIdle ? 1 : 0, packageName));
mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
@@ -769,10 +771,17 @@
if (mAppIdleParoled) {
return false;
}
- return isAppIdleFiltered(packageName, userId, timeNow);
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+ return isAppIdleFiltered(packageName, ai.uid, userId, timeNow);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ return false;
}
- boolean isAppIdleFiltered(String packageName, int userId, long timeNow) {
+ boolean isAppIdleFiltered(String packageName, int uidForAppId, int userId, long timeNow) {
final UserUsageStatsService userService;
final long screenOnTime;
synchronized (mLock) {
@@ -782,7 +791,8 @@
userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
screenOnTime = getScreenOnTimeLocked(timeNow);
}
- return isAppIdleFiltered(packageName, userId, userService, timeNow, screenOnTime);
+ return isAppIdleFiltered(packageName, UserHandle.getAppId(uidForAppId), userId,
+ userService, timeNow, screenOnTime);
}
/**
@@ -791,14 +801,22 @@
* This happens if the device is plugged in or temporarily allowed to make exceptions.
* Called by interface impls.
*/
- private boolean isAppIdleFiltered(String packageName, int userId,
+ private boolean isAppIdleFiltered(String packageName, int appId, int userId,
UserUsageStatsService userService, long timeNow, long screenOnTime) {
if (packageName == null) return false;
// If not enabled at all, of course nobody is ever idle.
if (!mAppIdleEnabled) {
return false;
}
- if (packageName.equals("android")) return false;
+ if (appId < Process.FIRST_APPLICATION_UID) {
+ // System uids never go idle.
+ return false;
+ }
+ if (packageName.equals("android")) {
+ // Nor does the framework (which should be redundant with the above, but for MR1 we will
+ // retain this for safety).
+ return false;
+ }
try {
// We allow all whitelisted apps, including those that don't want to be whitelisted
// for idle mode, because app idle (aka app standby) is really not as big an issue
@@ -865,8 +883,8 @@
ApplicationInfo ai = apps.get(i);
// Check whether this app is idle.
- boolean idle = isAppIdleFiltered(ai.packageName, userId, userService, timeNow,
- screenOnTime);
+ boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+ userId, userService, timeNow, screenOnTime);
int index = uidStates.indexOfKey(ai.uid);
if (index < 0) {
@@ -1352,8 +1370,8 @@
}
@Override
- public boolean isAppIdle(String packageName, int userId) {
- return UsageStatsService.this.isAppIdleFiltered(packageName, userId, -1);
+ public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+ return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId, -1);
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index e11c8d3..d1d6e0d 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -332,9 +332,24 @@
return 0;
}
+ /**
+ * @hide
+ */
+ public static String givePrintableIccid(String iccId) {
+ String iccIdToPrint = null;
+ if (iccId != null) {
+ if (iccId.length() > 9) {
+ iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX";
+ } else {
+ iccIdToPrint = iccId;
+ }
+ }
+ return iccIdToPrint;
+ }
+
@Override
public String toString() {
- String iccIdToPrint = mIccId != null ? mIccId.substring(0, 9) + "XXXXXXXXXXX" : null;
+ String iccIdToPrint = givePrintableIccid(mIccId);
return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
+ " displayName=" + mDisplayName + " carrierName=" + mCarrierName
+ " nameSource=" + mNameSource + " iconTint=" + mIconTint
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 861a379..5f84e0c 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -188,6 +188,20 @@
public static final String EXTRA_CODEC = "Codec";
public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+
+ /**
+ * Extra key which the RIL can use to indicate the radio technology used for a call.
+ * Valid values are:
+ * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
+ * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}, and the other defined
+ * {@code RIL_RADIO_TECHNOLOGY_*} constants.
+ * Note: Despite the fact the {@link android.telephony.ServiceState} values are integer
+ * constants, the values passed for the {@link #EXTRA_CALL_RAT_TYPE} should be strings (e.g.
+ * "14" vs (int) 14).
+ * Note: This is used by {@link com.android.internal.telephony.imsphone.ImsPhoneConnection#
+ * updateWifiStateFromExtras(Bundle)} to determine whether to set the
+ * {@link android.telecom.Connection#CAPABILITY_WIFI} capability on a connection.
+ */
public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
public int mServiceType;
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
index b4e0c70..4771b6c 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -95,11 +96,9 @@
private boolean mResumed;
// Drop one frame per half second.
- // TODO(khmel)
- // Add a feature flag and set the target FPS dependent on the target system as e.g.:
- // 59FPS for MULTI_WINDOW and 54 otherwise (to satisfy the default lax Android requirements).
private double mRefreshRate;
private double mTargetFPS;
+ private boolean mAndromeda;
private int mWidth;
private int mHeight;
@@ -182,6 +181,10 @@
return score;
}
+ public boolean isAndromeda() {
+ return mAndromeda;
+ }
+
@Override
public void onClick(View view) {
if (view == mMeasureCompositionButton) {
@@ -247,6 +250,9 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ // Detect Andromeda devices by having free-form window management feature.
+ mAndromeda = getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
detectRefreshRate();
// To layouts in parent. First contains list of Surfaces and second
@@ -513,7 +519,8 @@
}
MemoryInfo memInfo = getMemoryInfo();
- String info = "Available " +
+ String platformName = mAndromeda ? "Andromeda" : "Android";
+ String info = platformName + ": available " +
getReadableMemory(memInfo.availMem) + " from " +
getReadableMemory(memInfo.totalMem) + ".\nVisible " +
visibleCnt + " from " + mViews.size() + " " +
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
index 3f04888..388f91a 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
@@ -17,6 +17,7 @@
import android.app.Activity;
import android.graphics.PixelFormat;
+import android.os.Build;
import android.os.Bundle;
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
@@ -44,11 +45,16 @@
PixelFormat.OPAQUE,
};
- // Based on Nexus 9 performance which is usually < 9.0.
- private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE = new double[] {
+ // Nexus 9 performance is around 8.8. We distinguish results for Andromeda and
+ // Android devices. Andromeda devices require higher performance score.
+ private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA = new double[] {
8.0,
8.0,
};
+ private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID = new double[] {
+ 4.0,
+ 4.0,
+ };
// Based on Nexus 6 performance which is usually < 28.0.
private final static double[] MIN_ACCEPTED_ALLOCATION_SCORE = new double[] {
@@ -66,6 +72,8 @@
@SmallTest
public void testSurfaceCompositionPerformance() {
Bundle status = new Bundle();
+ double[] minScores = getActivity().isAndromeda() ?
+ MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA : MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID;
for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
int pixelFormat = TEST_PIXEL_FORMATS[i];
String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
@@ -73,8 +81,8 @@
Log.i(TAG, "testSurfaceCompositionPerformance(" + formatName + ") = " + score);
assertTrue("Device does not support surface(" + formatName + ") composition " +
"performance score. " + score.mSurfaces + " < " +
- MIN_ACCEPTED_COMPOSITION_SCORE[i] + ".",
- score.mSurfaces >= MIN_ACCEPTED_COMPOSITION_SCORE[i]);
+ minScores[i] + ". Build: " + Build.FINGERPRINT + ".",
+ score.mSurfaces >= minScores[i]);
// Send status only for TRANSLUCENT format.
if (pixelFormat == PixelFormat.TRANSLUCENT) {
status.putDouble(KEY_SURFACE_COMPOSITION_PERFORMANCE, score.mSurfaces);
@@ -96,7 +104,8 @@
Log.i(TAG, "testSurfaceAllocationPerformance(" + formatName + ") = " + score);
assertTrue("Device does not support surface(" + formatName + ") allocation " +
"performance score. " + score.mMedian + " < " +
- MIN_ACCEPTED_ALLOCATION_SCORE[i] + ".",
+ MIN_ACCEPTED_ALLOCATION_SCORE[i] + ". Build: " +
+ Build.FINGERPRINT + ".",
score.mMedian >= MIN_ACCEPTED_ALLOCATION_SCORE[i]);
// Send status only for TRANSLUCENT format.
if (pixelFormat == PixelFormat.TRANSLUCENT) {
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
index 36f4e73..2840826 100644
--- a/tools/aapt/ZipFile.cpp
+++ b/tools/aapt/ZipFile.cpp
@@ -364,7 +364,7 @@
long lfhPosn, startPosn, endPosn, uncompressedLen;
FILE* inputFp = NULL;
unsigned long crc;
- time_t modWhen;
+ time_t modWhen = 0;
if (mReadOnly)
return INVALID_OPERATION;
@@ -497,7 +497,6 @@
*/
pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
compressionMethod);
- modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
pEntry->setModWhen(modWhen);
pEntry->setLFHOffset(lfhPosn);
mEOCD.mNumEntries++;
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 275476c..ceed21e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -32,6 +32,7 @@
flatten/TableFlattener.cpp \
flatten/XmlFlattener.cpp \
link/AutoVersioner.cpp \
+ link/ManifestFixer.cpp \
link/PrivateAttributeMover.cpp \
link/ReferenceLinker.cpp \
link/TableMerger.cpp \
@@ -45,9 +46,11 @@
ConfigDescription.cpp \
Debug.cpp \
Flags.cpp \
- JavaClassGenerator.cpp \
+ java/AnnotationProcessor.cpp \
+ java/JavaClassGenerator.cpp \
+ java/ManifestClassGenerator.cpp \
+ java/ProguardRules.cpp \
Locale.cpp \
- ProguardRules.cpp \
Resource.cpp \
ResourceParser.cpp \
ResourceTable.cpp \
@@ -65,6 +68,7 @@
flatten/TableFlattener_test.cpp \
flatten/XmlFlattener_test.cpp \
link/AutoVersioner_test.cpp \
+ link/ManifestFixer_test.cpp \
link/PrivateAttributeMover_test.cpp \
link/ReferenceLinker_test.cpp \
link/TableMerger_test.cpp \
@@ -76,7 +80,8 @@
util/StringPiece_test.cpp \
util/Util_test.cpp \
ConfigDescription_test.cpp \
- JavaClassGenerator_test.cpp \
+ java/JavaClassGenerator_test.cpp \
+ java/ManifestClassGenerator_test.cpp \
Locale_test.cpp \
Resource_test.cpp \
ResourceParser_test.cpp \
@@ -110,7 +115,7 @@
endif
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
# ==========================================================
# Build the host static library: libaapt2
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 8120fa7..64353de 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -164,6 +164,26 @@
return false;
}
+static bool parseScreenRound(const char* name, ResTable_config* out) {
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_ANY;
+ return true;
+ } else if (strcmp(name, "round") == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_YES;
+ return true;
+ } else if (strcmp(name, "notround") == 0) {
+ if (out) out->screenLayout2 =
+ (out->screenLayout2&~ResTable_config::MASK_SCREENROUND)
+ | ResTable_config::SCREENROUND_NO;
+ return true;
+ }
+ return false;
+}
+
static bool parseOrientation(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->orientation = out->ORIENTATION_ANY;
@@ -635,6 +655,13 @@
}
}
+ if (parseScreenRound(partIter->c_str(), &config)) {
+ ++partIter;
+ if (partIter == partsEnd) {
+ goto success;
+ }
+ }
+
if (parseOrientation(partIter->c_str(), &config)) {
++partIter;
if (partIter == partsEnd) {
@@ -725,7 +752,9 @@
void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
uint16_t minSdk = 0;
- if (config->density == ResTable_config::DENSITY_ANY) {
+ if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+ minSdk = SDK_MARSHMALLOW;
+ } else if (config->density == ResTable_config::DENSITY_ANY) {
minSdk = SDK_LOLLIPOP;
} else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
|| config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index 8370816..e68d6be 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -15,6 +15,8 @@
*/
#include "ConfigDescription.h"
+#include "SdkConstants.h"
+
#include "util/StringPiece.h"
#include <gtest/gtest.h>
@@ -79,4 +81,19 @@
EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
}
+TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("round", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("round-v23"), config.toString().string());
+
+ EXPECT_TRUE(TestParse("notround", &config));
+ EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+ config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+ EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+ EXPECT_EQ(std::string("notround-v23"), config.toString().string());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 6ae5af7..9435396 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -16,6 +16,7 @@
#include "Flags.h"
#include "util/StringPiece.h"
+#include "util/Util.h"
#include <iomanip>
#include <iostream>
@@ -94,7 +95,14 @@
if (flag.numArgs > 0) {
argLine += " arg";
}
- *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
+
+ // Split the description by newlines and write out the argument (which is empty after
+ // the first line) followed by the description line. This will make sure that multiline
+ // descriptions are still right justified and aligned.
+ for (StringPiece line : util::tokenize<char>(flag.description, '\n')) {
+ *out << " " << std::setw(30) << std::left << argLine << line << "\n";
+ argLine = " ";
+ }
}
*out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
out->flush();
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
deleted file mode 100644
index 9f971fb..0000000
--- a/tools/aapt2/ManifestValidator.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#include "Logger.h"
-#include "ManifestValidator.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-ManifestValidator::ManifestValidator(const android::ResTable& table)
-: mTable(table) {
-}
-
-bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
- SourceLogger logger(source);
-
- android::ResXMLParser::event_code_t code;
- while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
- code != android::ResXMLParser::BAD_DOCUMENT) {
- if (code != android::ResXMLParser::START_TAG) {
- continue;
- }
-
- size_t len = 0;
- const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
- if (!namespaceUri.empty()) {
- continue;
- }
-
- const StringPiece16 name(parser->getElementName(&len), len);
- if (name.empty()) {
- logger.error(parser->getLineNumber())
- << "failed to get the element name."
- << std::endl;
- return false;
- }
-
- if (name == u"manifest") {
- if (!validateManifest(source, parser)) {
- return false;
- }
- }
- }
- return true;
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- const android::ResStringPool* pool = &parser->getStrings();
- if (value.dataType == android::Res_value::TYPE_REFERENCE) {
- ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
- if (strIdx < 0) {
- return {};
- }
- pool = mTable.getTableStringBlock(strIdx);
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
- return {};
- }
- return util::getString(*pool, value.data);
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING) {
- return {};
- }
- return util::getString(parser->getStrings(), value.data);
-}
-
-bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger,
- const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute with a string literal value."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute that points to a string."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
- const StringPiece16& attributeName,
- const StringPiece16& attributeValue,
- const StringPiece16& charSet, size_t lineNumber,
- SourceLogger& logger) {
- StringPiece16::const_iterator badIter =
- util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
- if (badIter != attributeValue.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' has invalid character '"
- << StringPiece16(badIter, 1)
- << "'."
- << std::endl;
- return false;
- }
-
- if (!attributeValue.empty()) {
- StringPiece16 trimmed = util::trimWhitespace(attributeValue);
- if (attributeValue.begin() != trimmed.begin()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not start with whitespace."
- << std::endl;
- return false;
- }
-
- if (attributeValue.end() != trimmed.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not end with whitespace."
- << std::endl;
- return false;
- }
- }
- return true;
-}
-
-constexpr const char16_t* kPackageIdentSet = u"._";
-
-bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
- bool error = false;
- SourceLogger logger(source);
-
- const StringPiece16 kAndroid = u"android";
- const StringPiece16 kPackage = u"package";
- const StringPiece16 kSharedUserId = u"sharedUserId";
-
- ssize_t idx;
-
- idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
- if (idx < 0) {
- logger.error(parser->getLineNumber())
- << "missing package attribute."
- << std::endl;
- error = true;
- } else {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
-
- idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(),
- kSharedUserId.data(), kSharedUserId.size());
- if (idx >= 0) {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
- return !error;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
deleted file mode 100644
index 1a7f48e..0000000
--- a/tools/aapt2/ManifestValidator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#ifndef AAPT_MANIFEST_VALIDATOR_H
-#define AAPT_MANIFEST_VALIDATOR_H
-
-#include "Logger.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-class ManifestValidator {
-public:
- ManifestValidator(const android::ResTable& table);
- ManifestValidator(const ManifestValidator&) = delete;
-
- bool validate(const Source& source, android::ResXMLParser* parser);
-
-private:
- bool validateManifest(const Source& source, android::ResXMLParser* parser);
-
- Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
- Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
-
- bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet);
- bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
- const StringPiece16& charSet);
- bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
- const StringPiece16& attributeValue, const StringPiece16& charSet,
- size_t lineNumber, SourceLogger& logger);
-
- const android::ResTable& mTable;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 1962f58..34dc1d5 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -36,7 +36,6 @@
case ResourceType::kFraction: return u"fraction";
case ResourceType::kId: return u"id";
case ResourceType::kInteger: return u"integer";
- case ResourceType::kIntegerArray: return u"integer-array";
case ResourceType::kInterpolator: return u"interpolator";
case ResourceType::kLayout: return u"layout";
case ResourceType::kMenu: return u"menu";
@@ -65,7 +64,6 @@
{ u"fraction", ResourceType::kFraction },
{ u"id", ResourceType::kId },
{ u"integer", ResourceType::kInteger },
- { u"integer-array", ResourceType::kIntegerArray },
{ u"interpolator", ResourceType::kInterpolator },
{ u"layout", ResourceType::kLayout },
{ u"menu", ResourceType::kMenu },
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 7ef1897..a7afbb5 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -47,7 +47,6 @@
kFraction,
kId,
kInteger,
- kIntegerArray,
kInterpolator,
kLayout,
kMenu,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 44710eb..2d6c0c2 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -62,6 +62,7 @@
StyleString* outStyleString) {
std::vector<Span> spanStack;
+ bool error = false;
outRawString->clear();
outStyleString->spans.clear();
util::StringBuilder builder;
@@ -84,7 +85,6 @@
spanStack.pop_back();
} else if (event == XmlPullParser::Event::kText) {
- // TODO(adamlesinski): Verify format strings.
outRawString->append(parser->getText());
builder.append(parser->getText());
@@ -116,9 +116,10 @@
if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "style string '" << builder.str() << "' is too long");
- return false;
+ error = true;
+ } else {
+ spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
}
- spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
} else if (event == XmlPullParser::Event::kComment) {
// Skip
@@ -129,7 +130,7 @@
assert(spanStack.empty() && "spans haven't been fully processed");
outStyleString->str = builder.str();
- return true;
+ return !error;
}
bool ResourceParser::parse(XmlPullParser* parser) {
@@ -308,6 +309,9 @@
} else if (elementName == u"dimen") {
parsedResource.name.type = ResourceType::kDimen;
result = parsePrimitive(parser, &parsedResource);
+ } else if (elementName == u"fraction") {
+ parsedResource.name.type = ResourceType::kFraction;
+ result = parsePrimitive(parser, &parsedResource);
} else if (elementName == u"style") {
parsedResource.name.type = ResourceType::kStyle;
result = parseStyle(parser, &parsedResource);
@@ -321,7 +325,7 @@
parsedResource.name.type = ResourceType::kArray;
result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
} else if (elementName == u"integer-array") {
- parsedResource.name.type = ResourceType::kIntegerArray;
+ parsedResource.name.type = ResourceType::kArray;
result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
} else if (elementName == u"declare-styleable") {
parsedResource.name.type = ResourceType::kStyleable;
@@ -434,13 +438,39 @@
bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- // TODO(adamlesinski): Read "untranslateable" attribute.
+ bool formatted = true;
+ if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+ if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
+ mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
+ return false;
+ }
+ }
+
+ bool untranslateable = false;
+ if (Maybe<StringPiece16> untranslateableAttr = findAttribute(parser, u"untranslateable")) {
+ if (!ResourceUtils::tryParseBool(untranslateableAttr.value(), &untranslateable)) {
+ mDiag->error(DiagMessage(source)
+ << "invalid value for 'untranslateable'. Must be a boolean");
+ return false;
+ }
+ }
outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
if (!outResource->value) {
mDiag->error(DiagMessage(source) << "not a valid string");
return false;
}
+
+ if (formatted || untranslateable) {
+ if (String* stringValue = valueCast<String>(outResource->value.get())) {
+ if (!util::verifyJavaStringFormat(*stringValue->value)) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?");
+ return false;
+ }
+ }
+ }
return true;
}
@@ -464,6 +494,8 @@
typeMask |= android::ResTable_map::TYPE_INTEGER;
break;
+ case ResourceType::kFraction:
+ // fallthrough
case ResourceType::kDimen:
typeMask |= android::ResTable_map::TYPE_DIMENSION
| android::ResTable_map::TYPE_FLOAT
@@ -576,6 +608,12 @@
return mask;
}
+/**
+ * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ */
+static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
+ return ns.empty() && (name == u"skip" || name == u"eat-comment");
+}
bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
outResource->source = mSource.withLine(parser->getLineNumber());
@@ -613,25 +651,30 @@
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
- // Skip comments and text.
+ if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ // Skip text.
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
- if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
+ if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) {
if (elementName == u"enum") {
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "can not define an <enum>; already defined a <flag>");
error = true;
continue;
}
typeMask |= android::ResTable_map::TYPE_ENUM;
+
} else if (elementName == u"flag") {
if (typeMask & android::ResTable_map::TYPE_ENUM) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "can not define a <flag>; already defined an <enum>");
error = true;
continue;
@@ -642,21 +685,22 @@
if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
ParsedResource childResource;
childResource.name = s.value().symbol.name.value();
- childResource.source = mSource.withLine(parser->getLineNumber());
+ childResource.source = itemSource;
childResource.value = util::make_unique<Id>();
outResource->childResources.push_back(std::move(childResource));
+
+ s.value().symbol.setComment(std::move(comment));
+ s.value().symbol.setSource(itemSource);
items.push_back(std::move(s.value()));
} else {
error = true;
}
- } else if (elementName == u"skip" || elementName == u"eat-comment") {
- comment = u"";
-
- } else {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << ":" << elementName << ">");
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
error = true;
}
+
+ comment = {};
}
if (error) {
@@ -716,11 +760,10 @@
p++;
}
- return ResourceName{ package.toString(), ResourceType::kAttr,
- name.empty() ? str.toString() : name.toString() };
+ return ResourceName(package.toString(), ResourceType::kAttr,
+ name.empty() ? str.toString() : name.toString());
}
-
bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
const Source source = mSource.withLine(parser->getLineNumber());
@@ -783,7 +826,6 @@
}
bool error = false;
- std::u16string comment;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
@@ -796,11 +838,7 @@
if (elementNamespace == u"" && elementName == u"item") {
error |= !parseStyleItem(parser, style.get());
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< ":" << elementName << ">");
error = true;
@@ -820,7 +858,6 @@
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Array> array = util::make_unique<Array>();
- std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -839,13 +876,10 @@
error = true;
continue;
}
+ item->setSource(itemSource);
array->items.emplace_back(std::move(item));
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "unknown tag <" << elementNamespace << ":" << elementName << ">");
error = true;
@@ -864,7 +898,6 @@
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -873,13 +906,14 @@
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"item") {
const auto endAttrIter = parser->endAttributes();
auto attrIter = parser->findAttribute(u"", u"quantity");
if (attrIter == endAttrIter || attrIter->value.empty()) {
- mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
+ mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
<< "'quantity'");
error = true;
continue;
@@ -900,7 +934,7 @@
} else if (trimmedQuantity == u"other") {
index = Plural::Other;
} else {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "<item> in <plural> has invalid value '" << trimmedQuantity
<< "' for attribute 'quantity'");
error = true;
@@ -908,7 +942,7 @@
}
if (plural->values[index]) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "duplicate quantity '" << trimmedQuantity << "'");
error = true;
continue;
@@ -918,11 +952,10 @@
kNoRawString))) {
error = true;
}
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
- } else {
- mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+ plural->values[index]->setSource(itemSource);
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
@@ -944,43 +977,52 @@
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
- // Ignore text and comments.
+ if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ // Ignore text.
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"attr") {
const auto endAttrIter = parser->endAttributes();
auto attrIter = parser->findAttribute(u"", u"name");
if (attrIter == endAttrIter || attrIter->value.empty()) {
- mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
+ // Create the ParsedResource that will add the attribute to the table.
ParsedResource childResource;
childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
- childResource.source = mSource.withLine(parser->getLineNumber());
+ childResource.source = itemSource;
+ childResource.comment = std::move(comment);
if (!parseAttrImpl(parser, &childResource, true)) {
error = true;
continue;
}
- styleable->entries.push_back(Reference(childResource.name));
+ // Create the reference to this attribute.
+ Reference childRef(childResource.name);
+ childRef.setComment(childResource.comment);
+ childRef.setSource(itemSource);
+ styleable->entries.push_back(std::move(childRef));
+
outResource->childResources.push_back(std::move(childResource));
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
- mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
+
+ comment = {};
}
if (error) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index af6bf67..2f5daae 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -414,6 +414,34 @@
EXPECT_EQ(value->getComment(), u"One");
}
+TEST_F(ResourceParserTest, ParseNestedComments) {
+ // We only care about declare-styleable and enum/flag attributes because comments
+ // from those end up in R.java
+ std::string input = R"EOF(
+ <declare-styleable name="foo">
+ <!-- The name of the bar -->
+ <attr name="barName" format="string|reference" />
+ </declare-styleable>
+
+ <attr name="foo">
+ <!-- The very first -->
+ <enum name="one" value="1" />
+ </attr>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(1u, styleable->entries.size());
+
+ EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment());
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(1u, attr->symbols.size());
+
+ EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment());
+}
+
/*
* Declaring an ID as public should not require a separate definition
* (as an ID has no value).
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 0db1c37..d3c3c10 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -74,9 +74,11 @@
return false;
}
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ if (outRef != nullptr) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
if (outCreate) {
*outCreate = create;
}
@@ -88,6 +90,10 @@
return false;
}
+bool isReference(const StringPiece16& str) {
+ return tryParseReference(str, nullptr, nullptr, nullptr);
+}
+
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
if (trimmedStr.empty()) {
@@ -322,18 +328,36 @@
return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
}
-std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+bool tryParseBool(const StringPiece16& str, bool* outValue) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
- uint32_t data = 0;
if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
- data = 0xffffffffu;
- } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") {
- return {};
+ if (outValue) {
+ *outValue = true;
+ }
+ return true;
+ } else if (trimmedStr == u"false" || trimmedStr == u"FALSE") {
+ if (outValue) {
+ *outValue = false;
+ }
+ return true;
}
- android::Res_value value = { };
- value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
- value.data = data;
- return util::make_unique<BinaryPrimitive>(value);
+ return false;
+}
+
+std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
+ bool result = false;
+ if (tryParseBool(str, &result)) {
+ android::Res_value value = {};
+ value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
+
+ if (result) {
+ value.data = 0xffffffffu;
+ } else {
+ value.data = 0;
+ }
+ return util::make_unique<BinaryPrimitive>(value);
+ }
+ return {};
}
std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 118a2ee..851edc8 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -49,11 +49,21 @@
bool* outCreate = nullptr, bool* outPrivate = nullptr);
/*
+ * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ */
+bool isReference(const StringPiece16& str);
+
+/*
* Returns true if the string was parsed as an attribute reference (?[package:]type/name),
* with `outReference` set to the parsed reference.
*/
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
+/**
+ * Returns true if the value is a boolean, putting the result in `outValue`.
+ */
+bool tryParseBool(const StringPiece16& str, bool* outValue);
+
/*
* Returns a Reference, or None Maybe instance if the string `str` was parsed as a
* valid reference to a style.
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index d957999..48dc521 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -69,10 +69,6 @@
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInteger);
- type = parseResourceType(u"integer-array");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kIntegerArray);
-
type = parseResourceType(u"interpolator");
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInterpolator);
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 803da03..282ed9a 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -42,6 +42,7 @@
SDK_KITKAT_WATCH = 20,
SDK_LOLLIPOP = 21,
SDK_LOLLIPOP_MR1 = 22,
+ SDK_MARSHMALLOW = 23,
};
size_t findAttributeSdkLevel(ResourceId id);
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
index d948775..b769c76 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/XmlDom.cpp
@@ -125,7 +125,7 @@
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
assert(!stack->nodeStack.empty());
- stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
stack->nodeStack.pop();
}
@@ -194,7 +194,7 @@
XML_ParserFree(parser);
if (stack.root) {
- return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
+ return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
}
return {};
}
@@ -317,6 +317,22 @@
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
+Element* findRootElement(Node* node) {
+ if (!node) {
+ return nullptr;
+ }
+
+ Element* el = nullptr;
+ while ((el = nodeCast<Element>(node)) == nullptr) {
+ if (node->children.empty()) {
+ return nullptr;
+ }
+ // We are looking for the first element, and namespaces can only have one child.
+ node = node->children.front().get();
+ }
+ return el;
+}
+
void Node::addChild(std::unique_ptr<Node> child) {
child->parent = this;
children.push_back(std::move(child));
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index c095f08..9a46bcb 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -34,6 +34,8 @@
namespace aapt {
namespace xml {
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
struct RawVisitor;
/**
@@ -132,6 +134,8 @@
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
const Source& source);
+Element* findRootElement(Node* node);
+
/**
* A visitor interface for the different XML Node subtypes. This will not traverse into
* children. Use Visitor for that.
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 38cda09..c1ff556 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -132,6 +132,32 @@
uint32_t count;
};
+/**
+ * A structure representing source data for a resource entry.
+ * Appears after an android::ResTable_entry or android::ResTable_map_entry.
+ *
+ * TODO(adamlesinski): This causes some issues when runtime code checks
+ * the size of an android::ResTable_entry. It assumes it is an
+ * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
+ * which may not be true if this structure is present.
+ */
+struct ResTable_entry_source {
+ /**
+ * File path reference.
+ */
+ android::ResStringPool_ref path;
+
+ /**
+ * Line number this resource was defined on.
+ */
+ uint32_t line;
+
+ /**
+ * Comment string reference.
+ */
+ android::ResStringPool_ref comment;
+};
+
struct Public_entry {
uint16_t entryId;
@@ -143,8 +169,7 @@
uint16_t state;
android::ResStringPool_ref key;
- android::ResStringPool_ref source;
- uint32_t sourceLine;
+ ResTable_entry_source source;
};
/**
@@ -173,28 +198,7 @@
* The index into the string pool where the name of this
* symbol exists.
*/
- uint32_t stringIndex;
-};
-
-/**
- * A structure representing the source of a resourc entry.
- * Appears after an android::ResTable_entry or android::ResTable_map_entry.
- *
- * TODO(adamlesinski): This causes some issues when runtime code checks
- * the size of an android::ResTable_entry. It assumes it is an
- * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
- * which may not be true if this structure is present.
- */
-struct ResTable_entry_source {
- /**
- * Index into the source string pool.
- */
- uint32_t pathIndex;
-
- /**
- * Line number this resource was defined on.
- */
- uint32_t line;
+ android::ResStringPool_ref name;
};
/**
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 47fa2a6..6b90fb2 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -54,9 +54,16 @@
struct FlatEntry {
ResourceEntry* entry;
Value* value;
+
+ // The entry string pool index to the entry's name.
uint32_t entryKey;
+
+ // The source string pool index to the source file path.
uint32_t sourcePathKey;
uint32_t sourceLine;
+
+ // The source string pool index to the comment.
+ uint32_t commentKey;
};
class SymbolWriter {
@@ -318,8 +325,9 @@
if (mOptions.useExtendedChunks) {
// Write the extra source block. This will be ignored by the Android runtime.
ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
- sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
+ sourceBlock->path.index = util::hostToDevice32(entry->sourcePathKey);
sourceBlock->line = util::hostToDevice32(entry->sourceLine);
+ sourceBlock->comment.index = util::hostToDevice32(entry->commentKey);
outEntry->size += sizeof(*sourceBlock);
}
@@ -486,12 +494,14 @@
publicEntry->entryId = util::hostToDevice32(entry->id.value());
publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
entry->name).getIndex());
- publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
+ publicEntry->source.path.index = util::hostToDevice32(mSourcePool->makeRef(
util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
if (entry->symbolStatus.source.line) {
- publicEntry->sourceLine = util::hostToDevice32(
+ publicEntry->source.line = util::hostToDevice32(
entry->symbolStatus.source.line.value());
}
+ publicEntry->source.comment.index = util::hostToDevice32(mSourcePool->makeRef(
+ entry->symbolStatus.comment).getIndex());
switch (entry->symbolStatus.state) {
case SymbolState::kPrivate:
@@ -565,13 +575,16 @@
lineNumber = value->getSource().line.value();
}
+ const StringPool::Ref commentRef = mSourcePool->makeRef(value->getComment());
+
configToEntryListMap[configValue.config]
.push_back(FlatEntry{
entry,
value,
keyIndex,
(uint32_t) sourceRef.getIndex(),
- lineNumber });
+ lineNumber,
+ (uint32_t) commentRef.getIndex() });
}
}
@@ -680,7 +693,7 @@
// Update the offsets to their final values.
if (symbolEntryData) {
for (SymbolWriter::Entry& entry : symbolOffsets) {
- symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
+ symbolEntryData->name.index = util::hostToDevice32(entry.name.getIndex());
// The symbols were all calculated with the packageBuffer offset. We need to
// add the beginning of the output buffer.
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 0000000..b36682d
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "java/AnnotationProcessor.h"
+#include "util/Util.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+void AnnotationProcessor::appendCommentLine(const std::string& comment) {
+ static const std::string sDeprecated = "@deprecated";
+ static const std::string sSystemApi = "@SystemApi";
+
+ if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) {
+ mDeprecated = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@Deprecated";
+ }
+
+ if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) {
+ mSystemApi = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@android.annotations.SystemApi";
+ }
+
+ if (mComment.empty()) {
+ mComment += mPrefix;
+ mComment += "/**";
+ }
+
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " * ";
+ mComment += std::move(comment);
+}
+
+void AnnotationProcessor::appendComment(const StringPiece16& comment) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece16 line : util::tokenize(comment, u'\n')) {
+ line = util::trimWhitespace(line);
+ if (!line.empty()) {
+ appendCommentLine(util::utf16ToUtf8(line));
+ }
+ }
+}
+
+void AnnotationProcessor::appendComment(const StringPiece& comment) {
+ for (StringPiece line : util::tokenize(comment, '\n')) {
+ line = util::trimWhitespace(line);
+ if (!line.empty()) {
+ appendCommentLine(line.toString());
+ }
+ }
+}
+
+std::string AnnotationProcessor::buildComment() {
+ if (!mComment.empty()) {
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " */";
+ }
+ return std::move(mComment);
+}
+
+std::string AnnotationProcessor::buildAnnotations() {
+ return std::move(mAnnotations);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
new file mode 100644
index 0000000..81a6f6e
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
+#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
+
+#include "util/StringPiece.h"
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Builds a JavaDoc comment from a set of XML comments.
+ * This will also look for instances of @SystemApi and convert them to
+ * actual Java annotations.
+ *
+ * Example:
+ *
+ * Input XML:
+ *
+ * <!-- This is meant to be hidden because
+ * It is system api. Also it is @deprecated
+ * @SystemApi
+ * -->
+ *
+ * Output JavaDoc:
+ *
+ * /\*
+ * * This is meant to be hidden because
+ * * It is system api. Also it is @deprecated
+ * * @SystemApi
+ * *\/
+ *
+ * Output Annotations:
+ *
+ * @Deprecated
+ * @android.annotation.SystemApi
+ *
+ */
+class AnnotationProcessor {
+public:
+ /**
+ * Creates an AnnotationProcessor with a given prefix for each line generated.
+ * This is usually a set of spaces for indentation.
+ */
+ AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) {
+ }
+
+ /**
+ * Adds more comments. Since resources can have various values with different configurations,
+ * we need to collect all the comments.
+ */
+ void appendComment(const StringPiece16& comment);
+ void appendComment(const StringPiece& comment);
+
+ /**
+ * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
+ * undefined results.
+ */
+ std::string buildComment();
+
+ /**
+ * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations()
+ * have undefined results.
+ */
+ std::string buildAnnotations();
+
+private:
+ std::string mPrefix;
+ std::string mComment;
+ std::string mAnnotations;
+ bool mDeprecated = false;
+ bool mSystemApi = false;
+
+ void appendCommentLine(const std::string& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
similarity index 61%
rename from tools/aapt2/JavaClassGenerator.cpp
rename to tools/aapt2/java/JavaClassGenerator.cpp
index cdf1b6a..dfd2ef6 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-#include "JavaClassGenerator.h"
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "ValueVisitor.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/JavaClassGenerator.h"
#include "util/StringPiece.h"
#include <algorithm>
@@ -108,13 +111,13 @@
std::sort(sortedAttributes.begin(), sortedAttributes.end());
// First we emit the array containing the IDs of each attribute.
- *out << " "
+ *out << " "
<< "public static final int[] " << transform(entryName) << " = {";
const size_t attrCount = sortedAttributes.size();
for (size_t i = 0; i < attrCount; i++) {
if (i % kAttribsPerLine == 0) {
- *out << "\n ";
+ *out << "\n ";
}
*out << sortedAttributes[i].first;
@@ -122,11 +125,11 @@
*out << ", ";
}
}
- *out << "\n };\n";
+ *out << "\n };\n";
// Now we emit the indices into the array.
for (size_t i = 0; i < attrCount; i++) {
- *out << " "
+ *out << " "
<< "public static" << finalModifier
<< " int " << transform(entryName);
@@ -140,6 +143,85 @@
}
}
+static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
+ const uint32_t typeMask = attr->typeMask;
+ if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ processor->appendComment(
+ "<p>May be a reference to another resource, in the form\n"
+ "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
+ "attribute in the form\n"
+ "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_STRING) {
+ processor->appendComment(
+ "<p>May be a string value, using '\\;' to escape characters such as\n"
+ "'\\n' or '\\uxxxx' for a unicode character;");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_INTEGER) {
+ processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ processor->appendComment(
+ "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
+ "\"<code>false</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_COLOR) {
+ processor->appendComment(
+ "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
+ "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
+ "\"<code>#<i>aarrggbb</i></code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FLOAT) {
+ processor->appendComment(
+ "<p>May be a floating point value, such as \"<code>1.2</code>\".");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ processor->appendComment(
+ "<p>May be a dimension value, which is a floating point number appended with a\n"
+ "unit such as \"<code>14.5sp</code>\".\n"
+ "Available units are: px (pixels), dp (density-independent pixels),\n"
+ "sp (scaled pixels based on preferred font size), in (inches), and\n"
+ "mm (millimeters).");
+ }
+
+ if (typeMask & android::ResTable_map::TYPE_FRACTION) {
+ processor->appendComment(
+ "<p>May be a fractional value, which is a floating point number appended with\n"
+ "either % or %p, such as \"<code>14.5%</code>\".\n"
+ "The % suffix always means a percentage of the base size;\n"
+ "the optional %p suffix provides a size relative to some parent container.");
+ }
+
+ if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
+ if (typeMask & android::ResTable_map::TYPE_FLAGS) {
+ processor->appendComment(
+ "<p>Must be one or more (separated by '|') of the following "
+ "constant values.</p>");
+ } else {
+ processor->appendComment("<p>Must be one of the following constant values.</p>");
+ }
+
+ processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
+ for (const Attribute::Symbol& symbol : attr->symbols) {
+ std::stringstream line;
+ line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
+ << "<td>" << std::hex << symbol.value << std::dec << "</td>"
+ << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
+ processor->appendComment(line.str());
+ }
+ processor->appendComment("</table>");
+ }
+}
+
bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
const ResourceTablePackage* package,
const ResourceTableType* type,
@@ -185,7 +267,33 @@
generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
entry->values.front().value.get()), out);
} else {
- *out << " " << "public static" << finalModifier
+ AnnotationProcessor processor(" ");
+ if (entry->symbolStatus.state != SymbolState::kUndefined) {
+ processor.appendComment(entry->symbolStatus.comment);
+ }
+
+ for (const auto& configValue : entry->values) {
+ processor.appendComment(configValue.value->getComment());
+ }
+
+ if (!entry->values.empty()) {
+ if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
+ // We list out the available values for the given attribute.
+ addAttributeFormatDoc(&processor, attr);
+ }
+ }
+
+ std::string comment = processor.buildComment();
+ if (!comment.empty()) {
+ *out << comment << "\n";
+ }
+
+ std::string annotations = processor.buildAnnotations();
+ if (!annotations.empty()) {
+ *out << annotations << "\n";
+ }
+
+ *out << " " << "public static" << finalModifier
<< " int " << transform(unmangledName) << " = " << id << ";\n";
}
}
@@ -210,11 +318,11 @@
} else {
typeStr = toString(type->type);
}
- *out << " public static final class " << typeStr << " {\n";
+ *out << " public static final class " << typeStr << " {\n";
if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
return false;
}
- *out << " }\n";
+ *out << " }\n";
}
}
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
similarity index 100%
rename from tools/aapt2/JavaClassGenerator.h
rename to tools/aapt2/java/JavaClassGenerator.h
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
similarity index 89%
rename from tools/aapt2/JavaClassGenerator_test.cpp
rename to tools/aapt2/java/JavaClassGenerator_test.cpp
index cc5e981..2dc387b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "JavaClassGenerator.h"
+#include "java/JavaClassGenerator.h"
#include "util/Util.h"
#include "test/Builders.h"
@@ -198,4 +198,35 @@
EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
}
+TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/foo", ResourceId(0x01010000))
+ .build();
+ test::getValue<Id>(table.get(), u"@android:id/foo")
+ ->setComment(std::u16string(u"This is a comment\n@deprecated"));
+
+ JavaClassGenerator generator(table.get(), {});
+
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string actual = out.str();
+
+ EXPECT_NE(std::string::npos, actual.find(
+ R"EOF(/**
+ * This is a comment
+ * @deprecated
+ */
+ @Deprecated
+ public static final int foo = 0x01010000;)EOF"));
+}
+
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+
+}
+
+TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
+
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 0000000..901a344
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "Source.h"
+#include "XmlDom.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/ManifestClassGenerator.h"
+#include "util/Maybe.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+ const StringPiece16& value) {
+ const StringPiece16 sep = u".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+ StringPiece16 result;
+ if (iter != value.end()) {
+ result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+ } else {
+ result = value;
+ }
+
+ if (result.empty()) {
+ diag->error(DiagMessage(source) << "empty symbol");
+ return {};
+ }
+
+ iter = util::findNonAlphaNumericAndNotInSet(result, u"_");
+ if (iter != result.end()) {
+ diag->error(DiagMessage(source)
+ << "invalid character '" << StringPiece16(iter, 1)
+ << "' in '" << result << "'");
+ return {};
+ }
+
+ if (*result.begin() >= u'0' && *result.begin() <= u'9') {
+ diag->error(DiagMessage(source) << "symbol can not start with a digit");
+ return {};
+ }
+
+ return result;
+}
+
+static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
+ std::ostream* out) {
+ xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name");
+ if (!attr) {
+ diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
+ return false;
+ }
+
+ Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
+ attr->value);
+ if (!result) {
+ return false;
+ }
+
+ *out << "\n";
+
+ if (!util::trimWhitespace(el->comment).empty()) {
+ AnnotationProcessor processor(" ");
+ processor.appendComment(el->comment);
+ *out << processor.buildComment() << "\n";
+ std::string annotations = processor.buildAnnotations();
+ if (!annotations.empty()) {
+ *out << annotations << "\n";
+ }
+ }
+ *out << " public static final String " << result.value() << "=\"" << attr->value << "\";\n";
+ return true;
+}
+
+bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
+ XmlResource* res, std::ostream* out) {
+ xml::Element* el = xml::findRootElement(res->root.get());
+ if (!el) {
+ return false;
+ }
+
+ if (el->name != u"manifest" && !el->namespaceUri.empty()) {
+ diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
+ return false;
+ }
+
+ *out << "package " << package << ";\n\n"
+ << "public class Manifest {\n";
+
+ bool error = false;
+ std::vector<xml::Element*> children = el->getChildElements();
+
+
+ // First write out permissions.
+ *out << " public static class permission {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ // Next write out permission groups.
+ *out << " public static class permission_group {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ *out << "}\n";
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
new file mode 100644
index 0000000..0f0998f
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+
+#include "Diagnostics.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <iostream>
+
+namespace aapt {
+
+struct ManifestClassGenerator {
+ bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+ std::ostream* out);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
new file mode 100644
index 0000000..1b5bc05
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "java/ManifestClassGenerator.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <permission android:name="android.DO_DANGEROUS_THINGS" />
+ <permission android:name="com.test.sample.permission.HUH" />
+ <permission-group android:name="foo.bar.PERMISSION" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ const size_t permissionClassPos = actual.find("public static class permission {");
+ const size_t permissionGroupClassPos = actual.find("public static class permission_group {");
+ ASSERT_NE(std::string::npos, permissionClassPos);
+ ASSERT_NE(std::string::npos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission class.
+ //
+
+ size_t pos = actual.find("public static final String ACCESS_INTERNET="
+ "\"android.permission.ACCESS_INTERNET\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission_group class
+ //
+
+ pos = actual.find("public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permissionGroupClassPos);
+ EXPECT_LT(pos, std::string::npos);
+}
+
+TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Required to access the internet.
+ Added in API 1. -->
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <!-- @deprecated This permission is for playing outside. -->
+ <permission android:name="android.permission.PLAY_OUTSIDE" />
+ <!-- This is a private permission for system only!
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SECRET" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * Required to access the internet.
+ * Added in API 1.
+ */
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * @deprecated This permission is for playing outside.
+ */
+ @Deprecated
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * This is a private permission for system only!
+ * @hide
+ * @SystemApi
+ */
+ @android.annotations.SystemApi
+ public static final String SECRET="android.permission.SECRET";)EOF"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
similarity index 91%
rename from tools/aapt2/ProguardRules.cpp
rename to tools/aapt2/java/ProguardRules.cpp
index 7f4dc91..4431477 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include "ProguardRules.h"
#include "XmlDom.h"
+#include "java/ProguardRules.h"
#include "util/Util.h"
#include <memory>
@@ -25,8 +25,6 @@
namespace aapt {
namespace proguard {
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
class BaseVisitor : public xml::Visitor {
public:
BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
@@ -83,7 +81,7 @@
bool checkName = false;
if (node->namespaceUri.empty()) {
checkClass = node->name == u"view" || node->name == u"fragment";
- } else if (node->namespaceUri == kSchemaAndroid) {
+ } else if (node->namespaceUri == xml::kSchemaAndroid) {
checkName = node->name == u"fragment";
}
@@ -91,10 +89,10 @@
if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
util::isJavaClassName(attr.value)) {
addClass(node->lineNumber, attr.value);
- } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
- util::isJavaClassName(attr.value)) {
+ } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
+ attr.name == u"name" && util::isJavaClassName(attr.value)) {
addClass(node->lineNumber, attr.value);
- } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") {
addMethod(node->lineNumber, attr.value);
}
}
@@ -114,7 +112,7 @@
}
if (checkFragment) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
if (attr && util::isJavaClassName(attr->value)) {
addClass(node->lineNumber, attr->value);
}
@@ -156,7 +154,7 @@
}
} else if (node->name == u"application") {
getName = true;
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
@@ -171,7 +169,7 @@
}
if (getName) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
similarity index 100%
rename from tools/aapt2/ProguardRules.h
rename to tools/aapt2/java/ProguardRules.h
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index ad701de..0236e98 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,16 +17,18 @@
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
-#include "JavaClassGenerator.h"
#include "NameMangler.h"
-#include "ProguardRules.h"
#include "XmlDom.h"
#include "compile/IdAssigner.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "java/JavaClassGenerator.h"
+#include "java/ManifestClassGenerator.h"
+#include "java/ProguardRules.h"
#include "link/Linkers.h"
+#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -53,6 +55,8 @@
bool verbose = false;
bool outputToDirectory = false;
Maybe<std::u16string> privateSymbols;
+ Maybe<std::u16string> minSdkVersionDefault;
+ Maybe<std::u16string> targetSdkVersionDefault;
};
struct LinkContext : public IAaptContext {
@@ -239,15 +243,8 @@
}
Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
- xml::Node* node = xmlRes->root.get();
-
- // Find the first xml::Element.
- while (node && !xml::nodeCast<xml::Element>(node)) {
- node = !node->children.empty() ? node->children.front().get() : nullptr;
- }
-
// Make sure the first element is <manifest> with package attribute.
- if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+ if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
return AppInfo{ packageAttr->value };
@@ -354,6 +351,36 @@
return true;
}
+ bool writeManifestJavaFile(XmlResource* manifestXml) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath,
+ file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
+ file::mkdirs(outPath);
+ file::appendPath(&outPath, "Manifest.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ ManifestClassGenerator generator;
+ if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
+ manifestXml, &fout)) {
+ return false;
+ }
+
+ if (!fout) {
+ mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
bool writeProguardFile(const proguard::KeepSet& keepSet) {
if (!mOptions.generateProguardRulesPath) {
return true;
@@ -539,15 +566,28 @@
}
{
+ ManifestFixerOptions manifestFixerOptions;
+ manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
+ manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
+ ManifestFixer manifestFixer(manifestFixerOptions);
+ if (!manifestFixer.consume(&mContext, manifestXml.get())) {
+ error = true;
+ }
+
XmlReferenceLinker manifestLinker;
if (manifestLinker.consume(&mContext, manifestXml.get())) {
-
if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
manifestXml.get(),
&proguardKeepSet)) {
error = true;
}
+ if (mOptions.generateJavaClassPath) {
+ if (!writeManifestJavaFile(manifestXml.get())) {
+ error = true;
+ }
+ }
+
if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
archiveWriter.get())) {
error = true;
@@ -705,6 +745,7 @@
int link(const std::vector<StringPiece>& args) {
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
+ Maybe<std::string> minSdkVersion, targetSdkVersion;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -720,10 +761,15 @@
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
"by -o",
&options.outputToDirectory)
+ .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
+ "AndroidManifest.xml", &minSdkVersion)
+ .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
+ "AndroidManifest.xml", &targetSdkVersion)
.optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
.optionalFlag("--private-symbols", "Package name to use when generating R.java for "
- "private symbols. If not specified, public and private symbols will "
- "use the application's package name", &privateSymbolsPackage)
+ "private symbols.\n"
+ "If not specified, public and private symbols will use the application's "
+ "package name", &privateSymbolsPackage)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -734,6 +780,14 @@
options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
}
+ if (minSdkVersion) {
+ options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
+ }
+
+ if (targetSdkVersion) {
+ options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
+ }
+
LinkCommand cmd = { options };
return cmd.run(flags.getArgs());
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
new file mode 100644
index 0000000..52d9426
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "ResourceUtils.h"
+#include "XmlDom.h"
+
+#include "link/ManifestFixer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) {
+ bool error = false;
+
+ xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+ if (!attr) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "missing 'package' attribute");
+ error = true;
+ } else if (ResourceUtils::isReference(attr->value)) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "value for attribute 'package' must not be a "
+ "reference");
+ error = true;
+ } else if (!util::isJavaPackageName(attr->value)) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "invalid package name '" << attr->value << "'");
+ error = true;
+ }
+
+ return !error;
+}
+
+static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el,
+ const ManifestFixerOptions& options) {
+ if (options.minSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() });
+ }
+
+ if (options.targetSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"targetSdkVersion",
+ options.targetSdkVersionDefault.value() });
+ }
+ return true;
+}
+
+bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+ xml::Element* root = xml::findRootElement(doc->root.get());
+ if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
+ context->getDiagnostics()->error(DiagMessage(doc->file.source)
+ << "root tag must be <manifest>");
+ return false;
+ }
+
+ if (!verifyManifest(context, doc->file.source, root)) {
+ return false;
+ }
+
+ bool foundUsesSdk = false;
+ for (xml::Element* el : root->getChildElements()) {
+ if (!el->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (el->name == u"uses-sdk") {
+ foundUsesSdk = true;
+ fixUsesSdk(context, doc->file.source, el, mOptions);
+ }
+ }
+
+ if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) {
+ std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
+ usesSdk->name = u"uses-sdk";
+ fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions);
+ root->addChild(std::move(usesSdk));
+ }
+
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
new file mode 100644
index 0000000..16e161d
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_LINK_MANIFESTFIXER_H
+#define AAPT_LINK_MANIFESTFIXER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+struct ManifestFixerOptions {
+ Maybe<std::u16string> minSdkVersionDefault;
+ Maybe<std::u16string> targetSdkVersionDefault;
+};
+
+/**
+ * Verifies that the manifest is correctly formed and inserts defaults
+ * where specified with ManifestFixerOptions.
+ */
+struct ManifestFixer : public IXmlResourceConsumer {
+ ManifestFixerOptions mOptions;
+
+ ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
+ }
+
+ bool consume(IAaptContext* context, XmlResource* doc) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
new file mode 100644
index 0000000..5c5d8af
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "link/ManifestFixer.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct ManifestFixerTest : public ::testing::Test {
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"android")
+ .setPackageId(0x01)
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .setSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
+ .addSymbol(u"@android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:string/str", ResourceId(0x01060000))
+ .build())
+ .build();
+ }
+
+ std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+ return verifyWithOptions(str, {});
+ }
+
+ std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
+ const ManifestFixerOptions& options) {
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+ ManifestFixer fixer(options);
+ if (fixer.consume(mContext.get(), doc.get())) {
+ return doc;
+ }
+ return {};
+ }
+};
+
+TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
+ EXPECT_EQ(nullptr, verify("<other-tag />"));
+ EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
+}
+
+TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+ "android:package=\"com.android\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
+}
+
+
+
+TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
+ ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
+
+ std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"7", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android" />)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 1b510e7..89cd972 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,8 +19,8 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "XmlDom.h"
+#include "util/Util.h"
#include "test/Common.h"
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 314c1e8..0d17e84 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -116,7 +116,7 @@
if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
// This offset is a symbol!
const StringPiece16 str = util::getString(
- mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex));
+ mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
StringPiece16 typeStr;
ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
@@ -425,8 +425,14 @@
Symbol symbol;
if (mSourcePool.getError() == NO_ERROR) {
symbol.source.path = util::utf16ToUtf8(util::getString(
- mSourcePool, util::deviceToHost32(entry->source.index)));
- symbol.source.line = util::deviceToHost32(entry->sourceLine);
+ mSourcePool, util::deviceToHost32(entry->source.path.index)));
+ symbol.source.line = util::deviceToHost32(entry->source.line);
+ }
+
+ StringPiece16 comment = util::getString(mSourcePool,
+ util::deviceToHost32(entry->source.comment.index));
+ if (!comment.empty()) {
+ symbol.comment = comment.toString();
}
switch (util::deviceToHost16(entry->state)) {
@@ -560,7 +566,7 @@
Source source = mSource;
if (sourceBlock) {
size_t len;
- const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex),
+ const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
&len);
if (str) {
source.path.assign(str, len);
@@ -568,6 +574,12 @@
source.line = util::deviceToHost32(sourceBlock->line);
}
+ StringPiece16 comment = util::getString(mSourcePool,
+ util::deviceToHost32(sourceBlock->comment.index));
+ if (!comment.empty()) {
+ resourceValue->setComment(comment);
+ }
+
resourceValue->setSource(source);
if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
mContext->getDiagnostics())) {
@@ -678,8 +690,6 @@
// fallthrough
case ResourceType::kAttr:
return parseAttr(name, config, map);
- case ResourceType::kIntegerArray:
- // fallthrough
case ResourceType::kArray:
return parseArray(name, config, map);
case ResourceType::kStyleable:
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index 8cbdeae..31deb45 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -36,6 +36,7 @@
class BasicStringPiece {
public:
using const_iterator = const TChar*;
+ using difference_type = size_t;
BasicStringPiece();
BasicStringPiece(const BasicStringPiece<TChar>& str);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index f219b65..59b8385 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -76,6 +76,25 @@
return StringPiece16(start, end - start);
}
+StringPiece trimWhitespace(const StringPiece& str) {
+ if (str.size() == 0 || str.data() == nullptr) {
+ return str;
+ }
+
+ const char* start = str.data();
+ const char* end = str.data() + str.length();
+
+ while (start != end && isspace(*start)) {
+ start++;
+ }
+
+ while (end != start && isspace(*(end - 1))) {
+ end--;
+ }
+
+ return StringPiece(start, end - start);
+}
+
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
const StringPiece16& allowedChars) {
const auto endIter = str.end();
@@ -170,6 +189,105 @@
return result;
}
+static size_t consumeDigits(const char16_t* start, const char16_t* end) {
+ const char16_t* c = start;
+ for (; c != end && *c >= u'0' && *c <= u'9'; c++) {}
+ return static_cast<size_t>(c - start);
+}
+
+bool verifyJavaStringFormat(const StringPiece16& str) {
+ const char16_t* c = str.begin();
+ const char16_t* const end = str.end();
+
+ size_t argCount = 0;
+ bool nonpositional = false;
+ while (c != end) {
+ if (*c == u'%' && c + 1 < end) {
+ c++;
+
+ if (*c == u'%') {
+ c++;
+ continue;
+ }
+
+ argCount++;
+
+ size_t numDigits = consumeDigits(c, end);
+ if (numDigits > 0) {
+ c += numDigits;
+ if (c != end && *c != u'$') {
+ // The digits were a size, but not a positional argument.
+ nonpositional = true;
+ }
+ } else if (*c == u'<') {
+ // Reusing last argument, bad idea since positions can be moved around
+ // during translation.
+ nonpositional = true;
+
+ c++;
+
+ // Optionally we can have a $ after
+ if (c != end && *c == u'$') {
+ c++;
+ }
+ } else {
+ nonpositional = true;
+ }
+
+ // Ignore size, width, flags, etc.
+ while (c != end && (*c == u'-' ||
+ *c == u'#' ||
+ *c == u'+' ||
+ *c == u' ' ||
+ *c == u',' ||
+ *c == u'(' ||
+ (*c >= u'0' && *c <= '9'))) {
+ c++;
+ }
+
+ /*
+ * This is a shortcut to detect strings that are going to Time.format()
+ * instead of String.format()
+ *
+ * Comparison of String.format() and Time.format() args:
+ *
+ * String: ABC E GH ST X abcdefgh nost x
+ * Time: DEFGHKMS W Za d hkm s w yz
+ *
+ * Therefore we know it's definitely Time if we have:
+ * DFKMWZkmwyz
+ */
+ if (c != end) {
+ switch (*c) {
+ case 'D':
+ case 'F':
+ case 'K':
+ case 'M':
+ case 'W':
+ case 'Z':
+ case 'k':
+ case 'm':
+ case 'w':
+ case 'y':
+ case 'z':
+ return true;
+ }
+ }
+ }
+
+ if (c != end) {
+ c++;
+ }
+ }
+
+ if (argCount > 1 && nonpositional) {
+ // Multiple arguments were specified, but some or all were non positional. Translated
+ // strings may rearrange the order of the arguments, which will break the string.
+ return false;
+ }
+ return true;
+}
+
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
char16_t code = 0;
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 402147d..80552a5 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -62,6 +62,8 @@
*/
StringPiece16 trimWhitespace(const StringPiece16& str);
+StringPiece trimWhitespace(const StringPiece& str);
+
/**
* UTF-16 isspace(). It basically checks for lower range characters that are
* whitespace.
@@ -156,6 +158,14 @@
return StringPiece16();
}
+/**
+ * Checks that the Java string format contains no non-positional arguments (arguments without
+ * explicitly specifying an index) when there are more than one argument. This is an error
+ * because translations may rearrange the order of the arguments in the string, which will
+ * break the string interpolation.
+ */
+bool verifyJavaStringFormat(const StringPiece16& str);
+
class StringBuilder {
public:
StringBuilder& append(const StringPiece16& str);
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index cdba960..9db9fb7 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -185,4 +185,12 @@
EXPECT_EQ(suffix, u".");
}
+TEST(UtilTest, VerifyJavaStringFormat) {
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%09.34f"));
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%9$.34f %8$"));
+ ASSERT_TRUE(util::verifyJavaStringFormat(u"%% %%"));
+ ASSERT_FALSE(util::verifyJavaStringFormat(u"%09$f %f"));
+ ASSERT_FALSE(util::verifyJavaStringFormat(u"%09f %08s"));
+}
+
} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 007d075..498be5a 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -513,4 +513,8 @@
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public void cancelTaskWindowTransition(int taskId) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 2997907..11bd15d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -95,6 +95,13 @@
}
@Override
+ public void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+ Rect outFrame) {
+ // pass for now.
+ return;
+ }
+
+ @Override
public void performDeferredDestroy(IWindow window) {
// pass for now.
}