diff options
162 files changed, 4377 insertions, 2278 deletions
diff --git a/api/current.txt b/api/current.txt index 3b4e3a9eeba2..62f0113a7d1c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -34268,6 +34268,18 @@ package android.util { 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 9db70e3cfa07..4118f338ed64 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -20523,6 +20523,7 @@ package android.net.http { 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 @@ package android.util { 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 12780a8742b9..7f33cb5df1a3 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 2960cdc9084e..ab781bbac6ba 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 static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO 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.IIntentSender; 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.InputStream; 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 @@ public final class Pm { 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 @@ public final class Pm { 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,473 +283,38 @@ public final class Pm { } } - /** - * Execute the list sub-command. - * - * pm list [package | packages] - * pm list permission-groups - * pm list permissions - * pm list features - * pm list libraries - * 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; - } - 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; + private int runShellCommand(String serviceName, String[] args) { + final HandlerThread handlerThread = new HandlerThread("results"); + handlerThread.start(); 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(); - } - } + ServiceManager.getService(serviceName).shellCommand( + FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, + args, new ResultReceiver(new Handler(handlerThread.getLooper()))); return 0; } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; + e.printStackTrace(); + } finally { + handlerThread.quitSafely(); } - } - - @SuppressWarnings("unchecked") - private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId) - throws RemoteException { - ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId); - return slice.getList(); + return -1; } /** - * Lists all of the features supported by the current device. + * Execute the list sub-command. * + * pm list [package | packages] + * pm list permission-groups + * pm list permissions * 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 + * pm list instrumentation */ - 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(""); - } + private int runList() { + final String type = nextArg(); + if ("users".equals(type)) { + return runShellCommand("user", new String[] { "list" }); } + return runShellCommand("package", mArgs); } private int runPath() { @@ -1467,29 +1018,6 @@ public final class Pm { } } - 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 @@ public final class Pm { 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 @@ public final class Pm { } 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 @@ public final class Pm { 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 2f0849f39aa2..472d97fdb9ed 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2728,8 +2728,8 @@ public class Activity extends ContextThemeWrapper /** * 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 203f34236723..b809baa28119 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -73,7 +73,6 @@ import java.util.List; */ public class ActivityManager { private static String TAG = "ActivityManager"; - private static boolean localLOGV = false; private static int gMaxRecentTasks = -1; @@ -397,60 +396,112 @@ public class ActivityManager { */ 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 diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 09c0a6e3ae46..77a9795bb59f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -653,7 +653,7 @@ public class AppOpsManager { 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 49edff411342..ffeb6ed32950 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3829,7 +3829,9 @@ public class Notification implements Parcelable @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 9e9d9496b461..0fdf3d3693c3 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2271,6 +2271,8 @@ public class DevicePolicyManager { * 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 948ea1effae7..498ff811cc74 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -71,10 +71,11 @@ public abstract class UsageStatsManagerInternal { * 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 27ecf9fc1309..c57fc89d19f8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1125,7 +1125,7 @@ public abstract class PackageManager { /** * 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 eb4cedab83b7..25ef8b500cda 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 class X509TrustManagerExtensions { 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/UserManager.java b/core/java/android/os/UserManager.java index 2e31ab692a51..e892349c34f6 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -487,6 +487,16 @@ public class UserManager { 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 d7be6d8c6b29..b50cc79ad341 100644 --- a/core/java/android/os/UserManagerInternal.java +++ b/core/java/android/os/UserManagerInternal.java @@ -36,7 +36,7 @@ public abstract class UserManagerInternal { * * 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 @@ public abstract class UserManagerInternal { * * 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 a67b3cb09f7a..4aac1962ebcf 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 @@ public abstract class FloatProperty<T> extends Property<T, Float> { } /** - * 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 17977ca63518..9e21cedf1b4b 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 @@ public abstract class IntProperty<T> extends Property<T, Integer> { } /** - * 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/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 017364a5a0b1..1be2f95cf55a 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -96,6 +96,23 @@ interface IWindowSession { 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 6de4d3e4e4c3..394660fb538b 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -56,6 +56,8 @@ public class Surface implements Parcelable { 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 @@ public class Surface implements Parcelable { } /** + * 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 bcf9b2c7fe7c..b58c68f782ee 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -81,6 +81,9 @@ public class SurfaceControl { 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 @@ public class SurfaceControl { 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 db68c29d025f..dddea210ed83 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -448,11 +448,10 @@ public class SurfaceView extends View { 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 @@ public class SurfaceView extends View { 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 8b804e8ddc8a..cd8c084ad14f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5455,7 +5455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @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 e17bdd7d9a3e..faeb35313174 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6607,6 +6607,20 @@ public final class ViewRootImpl implements ViewParent, } } + 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 5d11c8b7d888..825937289233 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -552,8 +552,8 @@ public abstract class Window { /** * 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 23e9a0df56fb..ab935050eb73 100644 --- a/core/java/android/webkit/WebResourceRequest.java +++ b/core/java/android/webkit/WebResourceRequest.java @@ -40,9 +40,9 @@ public interface WebResourceRequest { 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 584deff2659b..cb18b49b65f8 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -96,27 +96,49 @@ public final class WebViewFactory { public MissingWebViewPackageException(Exception e) { super(e); } } + /** @hide */ + public static String[] getWebViewPackageNames() { + return AppGlobals.getInitialApplication().getResources().getStringArray( + com.android.internal.R.array.config_webViewPackageNames); + } + + // TODO (gsennton) remove when committing webview xts test change public static String getWebViewPackageName() { - return AppGlobals.getInitialApplication().getString( - com.android.internal.R.string.config_webViewPackageName); + String[] webViewPackageNames = getWebViewPackageNames(); + return webViewPackageNames[webViewPackageNames.length-1]; } - private static PackageInfo fetchPackageInfo() { + /** + * 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 @@ public final class WebViewFactory { * 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 @@ public final class WebViewFactory { 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 13d046e45070..830da79c2a0b 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.net.LocalSocketAddress; 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 @@ public class InstallerConnection { 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 class InstallerConnection { } 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 b3bd46dd3869..1bce585c8358 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 static android.view.WindowManager.LayoutParams.*; 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * @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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 be9be11f9da6..de542b1bc65f 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 @@ public class NonClientDecorView extends LinearLayout 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 e6c7c2bcf9d7..703a9bd2fcd5 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 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { // 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 41aa9ca977fb..0a8ae2bdeecd 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -722,33 +722,33 @@ static jlong android_os_Parcel_getBlobAshmemSize(JNIEnv* env, jclass clazz, jlon // ---------------------------------------------------------------------------- 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 @@ static const JNINativeMethod gParcelMethods[] = { {"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 c94bc649014e..55b7e7ec8325 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -2156,9 +2156,9 @@ static const JNINativeMethod gAssetManagerMethods[] = { (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 @@ static const JNINativeMethod gAssetManagerMethods[] = { (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 @@ static const JNINativeMethod gAssetManagerMethods[] = { (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 @@ static const JNINativeMethod gAssetManagerMethods[] = { // 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 7ae51c89fab7..a15c23cd879a 100644 --- a/core/jni/android_util_XmlBlock.cpp +++ b/core/jni/android_util_XmlBlock.cpp @@ -372,37 +372,37 @@ static const JNINativeMethod gXmlBlockMethods[] = { (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 b64acc32e634..9b41eb3c659b 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -151,10 +151,10 @@ static jboolean android_view_DisplayListCanvas_isAvailable(JNIEnv* env, jobject // 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 da96b935883e..ff51e4ee1263 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -460,6 +460,10 @@ static jint nativeGetHeight(JNIEnv* env, jclass clazz, jlong nativeObject) { 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 @@ static const JNINativeMethod gSurfaceMethods[] = { (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 931ad54fb5e8..1dfe40a324b3 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -571,6 +571,21 @@ static jboolean nativeGetAnimationFrameStats(JNIEnv* env, jclass clazz, jobject 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 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (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 5828829511c1..561bcbc26d0c 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 400c8224b28e..76f70629c5ac 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 dd42c2278ba2..bfb0d103d7f2 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 d1932fc9061d..ba28e81e5794 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 000000000000..3638473aac03 --- /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 3c1036846595..6903b7b8a3fc 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 6a76a27c0bd3..bb515701ef8e 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -36,7 +36,6 @@ import com.android.frameworks.coretests.R; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; -import android.util.OrientationUtil; import android.view.KeyEvent; /** @@ -44,16 +43,13 @@ import android.view.KeyEvent; */ 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/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 26232a9bc89c..44d7530c6c11 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -382,7 +382,9 @@ public final class Icon implements Parcelable { * @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 340503074447..1ede1052d21b 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -1772,7 +1772,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { 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(); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index d94c91d64572..ae5fa6c15c01 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -56,6 +56,7 @@ hwui_src_files := \ Layer.cpp \ LayerCache.cpp \ LayerRenderer.cpp \ + LayerUpdateQueue.cpp \ Matrix.cpp \ OpenGLRenderer.cpp \ Patch.cpp \ @@ -204,6 +205,7 @@ LOCAL_SRC_FILES += \ 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 086885320cce..2fca5ea8b962 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 @@ namespace uirenderer { // 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 @@ OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t 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 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { // 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 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { 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 @@ void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) { 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 @@ void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, co 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 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR 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 16afad44657e..aa1e67d45d56 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -35,12 +35,24 @@ class RenderState; */ 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 @@ public: , 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 7c63e316ba38..94a11f131229 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 61e958d42148..330dc2951ec9 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 86796c5a5e0c..00c4e2d47e4c 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -154,7 +154,11 @@ public: return allocator.usedSize(); } bool isEmpty() { +#if HWUI_NEW_OPS + return ops.empty(); +#else return !hasDrawOps; +#endif } private: @@ -179,7 +183,7 @@ private: // 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 06c8a21b019b..6dd29ad8c703 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 0a30d162f2e8..6689b88f17e3 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 d2da8513ff56..f3ac93b89893 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -70,6 +70,20 @@ GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop // 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 6f5802eedefd..6270dcbe7a84 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -47,6 +47,7 @@ class GlopBuilder { 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 aa105f9fec0a..8c4645092c97 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 000000000000..db5f676d09dc --- /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 000000000000..be612d2a15e7 --- /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 ddeb33624798..163f7cc4b1d0 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 @@ private: 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::replayBakedOpsImpl(void* arg, BakedOpDispatche } 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::onViewportInitialized() {} 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 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { 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 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { 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 @@ void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { // 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::onEndLayerOp(const EndLayerOp& /* ignored */) { +void OpReorderer::restoreForLayer() { + // restore canvas, and pop finished layer off of the stack mCanvasState.restore(); + mLayerStack.pop_back(); +} - const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; +// 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); +} - // pop finished layer off of the stack +void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { + const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; 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 927ecfae3b9f..77be40292205 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -32,6 +32,7 @@ namespace uirenderer { class BakedOpState; class BatchBase; +class LayerUpdateQueue; class MergingOpBatch; class OffscreenBuffer; class OpBatch; @@ -64,9 +65,14 @@ class OpReorderer : public CanvasStateClient { */ 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 @@ class OpReorderer : public CanvasStateClient { 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 @@ class OpReorderer : public CanvasStateClient { }; 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 @@ public: // 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 @@ public: 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 4031f2e13f39..fd9ab1847f0d 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 c0c61db0f5d1..e81818679f3e 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 74cd74bde176..1293c786a0bd 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 7874d85f249c..9ae868a9858c 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 @@ struct EndLayerOp : RecordedOp { : 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 273af3ac5997..7c460b1bbc1a 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -77,7 +77,6 @@ SkCanvas* RecordingCanvas::asSkCanvas() { // ---------------------------------------------------------------------------- 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 454ee24cfc64..8a564758e7b1 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 8beed2540e1c..11d7a6af3a6a 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 39cb8e9229b1..0601944905a8 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 android { 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 @@ RenderNode::RenderNode() , 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 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { } } +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 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { 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 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { 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 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { // 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 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { 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 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { 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 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { } } +#if !HWUI_NEW_OPS void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { if (CC_LIKELY(!mLayer)) return; @@ -384,6 +423,7 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { 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::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL void RenderNode::destroyHardwareResources() { if (mLayer) { - LayerRenderer::destroyLayer(mLayer); + destroyLayer(mLayer); mLayer = nullptr; } if (mDisplayList) { @@ -978,7 +1018,11 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { 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 57e41c611547..3500cb200a51 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -44,13 +44,22 @@ namespace android { 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 @@ public: return mStagingProperties; } - int getWidth() { + uint32_t getWidth() { return properties().getWidth(); } - int getHeight() { + uint32_t getHeight() { return properties().getHeight(); } @@ -193,9 +202,13 @@ public: } // 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 @@ private: 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 @@ private: // 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 1c3148726b63..be25516c587a 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 CanvasContext; } class DamageAccumulator; +class LayerUpdateQueue; class OpenGLRenderer; class RenderState; @@ -75,9 +76,14 @@ public: // 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 43f170f54d41..7b8d0e542a2f 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -56,7 +56,9 @@ void BM_OpReorderer_defer::Run(int iters) { 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 0521f6573e39..03cb5ce8ce2e 100644 --- a/libs/hwui/renderstate/MeshState.cpp +++ b/libs/hwui/renderstate/MeshState.cpp @@ -100,6 +100,24 @@ bool MeshState::bindMeshBufferInternal(GLuint buffer) { 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 e80f4d0d6c41..6c0fb78cae17 100644 --- a/libs/hwui/renderstate/MeshState.h +++ b/libs/hwui/renderstate/MeshState.h @@ -75,6 +75,9 @@ public: */ 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 c1f667024784..b6fecb45a8ad 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 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, 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 @@ void CanvasContext::draw() { 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 @@ void CanvasContext::buildLayer(RenderNode* node) { 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 30e6562526d5..d656014fdbcb 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 @@ public: 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 @@ public: 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 @@ private: 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 15ccd6ac5b6b..a1107f029691 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -563,10 +563,7 @@ void RenderProxy::post(RenderTask* task) { 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 338fab650876..d0e601e09be6 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -117,9 +117,6 @@ private: 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 8fcd10967e17..526a84861d98 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 @@ public: } }; -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::queue(RenderTask* task) { } 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 f3444a85a336..d8c7e61f34eb 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 @@ protected: ~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 @@ protected: virtual bool threadLoop() override; private: - friend class Singleton<RenderThread>; friend class DispatchFrameCallbacks; friend class RenderProxy; friend class android::uirenderer::TestUtils; @@ -108,6 +107,9 @@ private: 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 @@ private: 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 2eefd37561a6..29d9803ddf9c 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 @@ static Benchmark _SaveLayer(BenchmarkInfo{ "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 fb760ac549cd..3ef329a0d51b 100644 --- a/libs/hwui/unit_tests/FatVectorTests.cpp +++ b/libs/hwui/unit_tests/FatVectorTests.cpp @@ -56,6 +56,27 @@ TEST(FatVector, simpleAllocate) { } } +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 @@ TEST(FatVector, destructorInternal) { 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 @@ TEST(FatVector, destructorExternal) { 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 000000000000..9d625bc62696 --- /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 ffb575f2ddd9..09b10c3af4f9 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 @@ namespace uirenderer { 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 @@ TEST(OpReorderer, renderNode) { 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 @@ TEST(OpReorderer, clipped) { 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 @@ TEST(OpReorderer, clipped) { 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 @@ TEST(OpReorderer, saveLayerSimple) { /* 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 @@ TEST(OpReorderer, saveLayerContentRejection) { 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 e8cdf461c783..dcf1f648a9b5 100644 --- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -53,7 +53,7 @@ TEST(RecordingCanvas, testSimpleRectRecord) { 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 @@ TEST(RecordingCanvas, backgroundAndImage) { } count++; }); - ASSERT_EQ(2, count); // two draws observed + ASSERT_EQ(2, count); } TEST(RecordingCanvas, saveLayerSimple) { @@ -121,7 +121,9 @@ TEST(RecordingCanvas, saveLayerSimple) { 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 @@ TEST(RecordingCanvas, saveLayerSimple) { 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 @@ TEST(RecordingCanvas, saveLayerViewportCrop) { 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 @@ TEST(RecordingCanvas, saveLayerRotateUnclipped) { 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 5b09fdac4ff2..770f413352c2 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/unit_tests/TestUtils.h @@ -89,15 +89,24 @@ public: 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; + } + + 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); - CanvasType canvas( - node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); + 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 @@ public: 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 @@ public: 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 c3c16c5ae27d..315c24978a1f 100644 --- a/libs/hwui/utils/FatVector.h +++ b/libs/hwui/utils/FatVector.h @@ -91,6 +91,10 @@ public: 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/AudioManager.java b/media/java/android/media/AudioManager.java index 875e7165fa81..50df55630a6a 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -3199,8 +3199,10 @@ public class AudioManager { /** * 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 4019d02da330..ada7f49dbd1f 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 a09a22a19037..3b7da78ccb8d 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.LayoutManager; 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { } }); - // 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { getActivity().getWindow().setStatusBarColor(color.data); if (mActionMode != null) { - mActionMode.setTitle(TextUtils.formatSelectedCount(mSelected.size())); + mActionMode.setTitle(String.valueOf(mSelected.size())); } } @@ -897,24 +905,26 @@ public class DirectoryFragment extends Fragment { // 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { } } - 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 000000000000..0eb1ea55895e --- /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 14a33f9c1cc1..26a373403d80 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.provider.DocumentsContract; 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.Arrays; 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 @@ public class ManageRootActivity extends BaseActivity { @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 ef53d53c2f78..4fde6ff74fea 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java @@ -76,6 +76,9 @@ public final class MultiSelectManager implements View.OnKeyListener { 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 @@ public final class MultiSelectManager implements View.OnKeyListener { 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 d75b6fdf77bc..beff196509b2 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.os.Bundle; 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 @@ import java.util.Objects; */ 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 @@ public class RootsFragment extends Fragment { } 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 2a540d4d77e9..b65ac98d0193 100644 --- a/packages/DocumentsUI/tests/Android.mk +++ b/packages/DocumentsUI/tests/Android.mk @@ -8,7 +8,7 @@ LOCAL_MODULE_TAGS := tests 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 fc42c3b12545..6f1a89baf525 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.net.Uri; 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 android.util.Log; 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,70 +54,19 @@ import java.util.concurrent.TimeoutException; public class CopyTest extends ServiceTestCase<CopyService> { - /** - * A test resolver that enables this test suite to listen for notifications that mark when copy - * operations are done. - */ - class TestContentResolver extends MockContentResolver { - private CountDownLatch mReadySignal; - private CountDownLatch mNotificationSignal; - - public TestContentResolver() { - mReadySignal = new CountDownLatch(1); - } - - /** - * Wait for the given number of files to be copied to destination. Times out after 1 sec. - */ - public void waitForChanges(int count) throws Exception { - // Wait for no more than 1 second by default. - waitForChanges(count, 1000); - } - - /** - * Wait for files to be copied to destination. - * - * @param count Number of files to wait for. - * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out. - */ - public void waitForChanges(int count, int timeOut) throws Exception { - mNotificationSignal = new CountDownLatch(count); - // Signal that the test is now waiting for files. - mReadySignal.countDown(); - if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) { - throw new TimeoutException("Timed out waiting for file operations to complete."); - } - } - - @Override - public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { - // Wait until the test is ready to receive file notifications. - try { - mReadySignal.await(); - } catch (InterruptedException e) { - Log.d(TAG, "Interrupted while waiting for file copy readiness"); - Thread.currentThread().interrupt(); - } - if (DocumentsContract.isDocumentUri(mContext, uri)) { - Log.d(TAG, "Notification: " + uri); - // Watch for document URI change notifications - this signifies the end of a copy. - mNotificationSignal.countDown(); - } - } - }; - 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 SRC_ROOT = StubProvider.ROOT_0_ID; + private static String DST_ROOT = StubProvider.ROOT_1_ID; private static String TAG = "CopyTest"; - private List<RootInfo> mRoots; + private Context mContext; private TestContentResolver mResolver; private ContentProviderClient mClient; + private DocumentsProviderHelper mDocHelper; private StubProvider mStorage; private Context mSystemContext; @@ -129,18 +80,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { // 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); - } - + mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient); } @Override @@ -154,7 +94,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { */ public void testCopyFile() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", + Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); assertDstFileCountEquals(0); @@ -172,7 +112,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { 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()); + Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes()); assertDstFileCountEquals(0); @@ -185,9 +125,9 @@ public class CopyTest extends ServiceTestCase<CopyService> { // Verify that one file was moved; check file contents. assertDstFileCountEquals(1); - assertDoesNotExist(SRC, srcPath); + assertDoesNotExist(SRC_ROOT, srcPath); - byte[] dstContent = readFile(DST, srcPath); + byte[] dstContent = readFile(DST_ROOT, srcPath); MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent); } @@ -206,9 +146,9 @@ public class CopyTest extends ServiceTestCase<CopyService> { "/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())); + 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); @@ -226,7 +166,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { public void testCopyEmptyDir() throws Exception { String srcPath = "/emptyDir"; - Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, + Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, null); assertDstFileCountEquals(0); @@ -239,13 +179,13 @@ public class CopyTest extends ServiceTestCase<CopyService> { assertDstFileCountEquals(1); // Verify that the dst exists and is a directory. - File dst = mStorage.getFile(DST, srcPath); + File dst = mStorage.getFile(DST_ROOT, srcPath); assertTrue(dst.isDirectory()); } public void testMoveEmptyDir() throws Exception { String srcPath = "/emptyDir"; - Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, + Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, null); assertDstFileCountEquals(0); @@ -260,11 +200,11 @@ public class CopyTest extends ServiceTestCase<CopyService> { assertDstFileCountEquals(1); // Verify that the dst exists and is a directory. - File dst = mStorage.getFile(DST, srcPath); + File dst = mStorage.getFile(DST_ROOT, srcPath); assertTrue(dst.isDirectory()); // Verify that the src was cleaned up. - assertDoesNotExist(SRC, srcPath); + assertDoesNotExist(SRC_ROOT, srcPath); } public void testMovePopulatedDir() throws Exception { @@ -280,11 +220,11 @@ public class CopyTest extends ServiceTestCase<CopyService> { srcDir + "/test2.txt" }; // Create test dir; put some files in it. - Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR, + Uri testDir = mStorage.createFile(SRC_ROOT, 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()); + 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); @@ -295,24 +235,24 @@ public class CopyTest extends ServiceTestCase<CopyService> { mResolver.waitForChanges(11); // Check the content of the moved files. - File dst = mStorage.getFile(DST, srcDir); + File dst = mStorage.getFile(DST_ROOT, srcDir); assertTrue(dst.isDirectory()); for (int i = 0; i < testContent.length; ++i) { - byte[] dstContent = readFile(DST, srcFiles[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, srcDir); + assertDoesNotExist(SRC_ROOT, srcDir); for (String srcFile : srcFiles) { - assertDoesNotExist(SRC, srcFile); + assertDoesNotExist(SRC_ROOT, srcFile); } } public void testCopyFileWithReadErrors() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", + Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); assertDstFileCountEquals(0); @@ -330,7 +270,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { public void testMoveFileWithReadErrors() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", + Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); assertDstFileCountEquals(0); @@ -352,7 +292,7 @@ public class CopyTest extends ServiceTestCase<CopyService> { } finally { // Verify that the failed copy was cleaned up, and the src file wasn't removed. assertDstFileCountEquals(0); - assertExists(SRC, srcPath); + assertExists(SRC_ROOT, srcPath); } // The asserts above didn't fail, but the CopyService did something unexpected. fail("Extra file operations were detected"); @@ -371,12 +311,12 @@ public class CopyTest extends ServiceTestCase<CopyService> { srcDir + "/test2.txt" }; // Create test dir; put some files in it. - Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR, + Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR, null); - mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes()); + mStorage.createFile(SRC_ROOT, 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()); + .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes()); + mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes()); mStorage.simulateReadErrorsForFile(errFile); @@ -391,22 +331,22 @@ public class CopyTest extends ServiceTestCase<CopyService> { // 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()); + 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, srcFiles[0])); + readFile(DST_ROOT, srcFiles[0])); MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(), - readFile(DST, srcFiles[2])); + readFile(DST_ROOT, srcFiles[2])); // Check that the src files were removed. - assertDoesNotExist(SRC, srcFiles[0]); - assertDoesNotExist(SRC, srcFiles[2]); + assertDoesNotExist(SRC_ROOT, srcFiles[0]); + assertDoesNotExist(SRC_ROOT, srcFiles[2]); // Check that the error file was not copied over. - assertDoesNotExist(DST, srcFiles[1]); - assertExists(SRC, srcFiles[1]); + assertDoesNotExist(DST_ROOT, srcFiles[1]); + assertExists(SRC_ROOT, srcFiles[1]); } /** @@ -414,13 +354,14 @@ public class CopyTest extends ServiceTestCase<CopyService> { * * @throws FileNotFoundException */ - private Intent createCopyIntent(List<Uri> srcs) 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)); } - final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId); + 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); @@ -435,8 +376,9 @@ public class CopyTest extends ServiceTestCase<CopyService> { * 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, - mRoots.get(1).documentId); + dest.documentId); Cursor c = null; int count = 0; try { @@ -474,8 +416,8 @@ public class CopyTest extends ServiceTestCase<CopyService> { } private void assertCopied(String path) throws Exception { - MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC, path), - readFile(DST, path)); + MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC_ROOT, path), + readFile(DST_ROOT, path)); } /** @@ -509,4 +451,56 @@ public class CopyTest extends ServiceTestCase<CopyService> { 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. + */ + class TestContentResolver extends MockContentResolver { + private CountDownLatch mReadySignal; + private CountDownLatch mNotificationSignal; + + public TestContentResolver() { + mReadySignal = new CountDownLatch(1); + } + + /** + * Wait for the given number of files to be copied to destination. Times out after 1 sec. + */ + public void waitForChanges(int count) throws Exception { + // Wait for no more than 1 second by default. + waitForChanges(count, 1000); + } + + /** + * Wait for files to be copied to destination. + * + * @param count Number of files to wait for. + * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out. + */ + public void waitForChanges(int count, int timeOut) throws Exception { + mNotificationSignal = new CountDownLatch(count); + // Signal that the test is now waiting for files. + mReadySignal.countDown(); + if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("Timed out waiting for file operations to complete."); + } + } + + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + // Wait until the test is ready to receive file notifications. + try { + mReadySignal.await(); + } catch (InterruptedException e) { + Log.d(TAG, "Interrupted while waiting for file copy readiness"); + Thread.currentThread().interrupt(); + } + if (DocumentsContract.isDocumentUri(mContext, uri)) { + Log.d(TAG, "Notification: " + uri); + // Watch for document URI change notifications - this signifies the end of a copy. + mNotificationSignal.countDown(); + } + } + }; } 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 000000000000..7abc99c99a5a --- /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 1f4b751abc85..906051640c6a 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(); + + mBot = new UiBot(mDevice, TIMEOUT); + + resetStorage(); // Just incase a test failed and tearDown didn't happen. + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + Log.d(TAG, "Resetting storage from setUp"); + resetStorage(); + mClient.release(); + } + + private void resetStorage() throws RemoteException { + mClient.call("clear", null, null); + // TODO: Would be nice to have an event to wait on here. mDevice.waitForIdle(); + } + + private void initTestFiles() throws RemoteException { + mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID); + mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID); + + 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"); + } - mBar = new ActionBar(); + 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 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)); - } + 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"); } - private class ActionBar { - - 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); - } - - 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); - } + 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 6a2e03a3b809..2d42ddc4e6b5 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.HashMap; 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 @@ public class StubProvider extends DocumentsProvider { 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 @@ public class StubProvider extends DocumentsProvider { @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 @@ public class StubProvider extends DocumentsProvider { */ 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 @@ public class StubProvider extends DocumentsProvider { 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 @@ public class StubProvider extends DocumentsProvider { } @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."); + } + if (!parent.file.isDirectory()) { + throw new IllegalArgumentException( + "Can't create file " + displayName + " inside non-directory parent " + + parent.file.getName()); } - final File file = new File(parentDocument.file, displayName); + + 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(); + throw new FileNotFoundException( + "Failed to create directory(s): " + file); } + Log.i(TAG, "Created new directory: " + file); } else { + boolean created = false; try { - if (!file.createNewFile()) { - throw new IllegalStateException("The file " + file.getPath() + " already exists"); - } + created = file.createNewFile(); } catch (IOException e) { - throw new FileNotFoundException(); + // 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, parentDocument); + 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 @@ public class StubProvider extends DocumentsProvider { 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 @@ public class StubProvider extends DocumentsProvider { 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 @@ public class StubProvider extends DocumentsProvider { @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 @@ public class StubProvider extends DocumentsProvider { @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 @@ public class StubProvider extends DocumentsProvider { 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 @@ public class StubProvider extends DocumentsProvider { 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 000000000000..5c09794a3e28 --- /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/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index 0d1ad197e3fa..e90a3b5a4824 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -136,9 +136,10 @@ public class BugreportReceiver extends BroadcastReceiver { // 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 794e9005fa31..049754ebaaef 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 @@ package com.android.systemui.qs; 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.FrameLayout; 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 @@ public class QSPanel extends FrameLayout implements Tunable { 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 61695b2db2d7..48b74a4566a5 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.internal.logging.MetricsLogger; 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 @@ public class CastTile extends QSTile<QSTile.BooleanState> { @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 c6fc6ffcb4d7..2f9a496d9602 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 android.provider.Settings.Secure; 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 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> { 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 7b83e6a9ca25..79084ae73e3c 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 android.content.Intent; 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 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> { @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 e6fade455444..0e2672ca4da1 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 @@ package com.android.systemui.qs.tiles; 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 @@ public class LocationTile extends QSTile<QSTile.BooleanState> { // 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 4d40cb7072f7..a58bc5882f2a 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.Context; 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 class Recents extends SystemUI 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 @@ public class Recents extends SystemUI 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 @@ public class Recents extends SystemUI */ @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 @@ public class Recents extends SystemUI */ @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 @@ public class Recents extends SystemUI */ @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 @@ public class Recents extends SystemUI } 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/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 243c0e1dbbf3..0e11f02a34ec 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 28299d3217fb..d4158451b54b 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.misc.SystemServicesProxy; 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 ea6821fe1696..515c3bda3f9c 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 class ParametricCurve { 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 class ParametricCurve { 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 81a949d1b7cf..2fe5e988b3e6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -72,8 +72,10 @@ import java.util.Iterator; 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 @@ -233,6 +235,15 @@ public class SystemServicesProxy { 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) { @@ -311,14 +322,14 @@ public class SystemServicesProxy { * 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; } /** @@ -728,7 +739,7 @@ public class SystemServicesProxy { 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/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java index ce993c537e76..a97a2a800ac4 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 @@ public class FreeformWorkspaceLayoutAlgorithm { // 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 @@ public class FreeformWorkspaceLayoutAlgorithm { * 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 @@ public class FreeformWorkspaceLayoutAlgorithm { /** * 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 @@ public class FreeformWorkspaceLayoutAlgorithm { 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 a5b7aaf3036a..d72e50e0c5dc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -61,7 +61,9 @@ import com.android.systemui.recents.model.TaskStack; 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 @@ -489,14 +491,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV 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)); 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 45d626e15327..a0713d7defc0 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.R; 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { // 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 @@ public class TaskStackLayoutAlgorithm { // 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 @@ public class TaskStackLayoutAlgorithm { // The freeform workspace layout FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; - // Temporary task view transform - TaskViewTransform mTmpTransform = new TaskViewTransform(); - // Log function static ParametricCurve sCurve; @@ -190,53 +191,57 @@ public class TaskStackLayoutAlgorithm { } /** - * 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { } 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 class TaskStackLayoutAlgorithm { */ 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 @@ public class TaskStackLayoutAlgorithm { // 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { } /** - * 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 @@ public class TaskStackLayoutAlgorithm { * 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 @@ public class TaskStackLayoutAlgorithm { * 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 b266eaacae10..14d75a85000d 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.HashSet; 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (boundTranslationsToRect) { transform.translationY = Math.min(transform.translationY, - mLayoutAlgorithm.mStackRect.bottom); + mLayoutAlgorithm.mCurrentStackRect.bottom); } prevTransform = transform; } @@ -344,13 +353,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + /** + * 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 6b92aeda1562..3a2ed0fa7d75 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 @@ public class TaskStackViewScroller { /** 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 @@ public class TaskStackViewScroller { // 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 9e6fb7bae4b9..59c9708840e2 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 @@ package com.android.systemui.recents.views; 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 android.view.ViewParent; 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 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { 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 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { 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/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java index 4ac2c3185b6d..a51f62ad0b69 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 ebe77853c1b2..fafedc33f0b1 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 @@ public abstract class PanelView extends FrameLayout { }; protected void onExpandingFinished() { - endClosing(); mBar.onExpandingFinished(); } @@ -143,6 +142,7 @@ public abstract class PanelView extends FrameLayout { } protected final void notifyExpandingFinished() { + endClosing(); if (mExpanding) { mExpanding = false; onExpandingFinished(); diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 85187c776401..a7879c64bc13 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -562,6 +562,8 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub } private void handleSystemReady() { - synchronized (mLock) { - resetIfReadyAndConnectedLocked(); - } + resetIfReadyAndConnected(); // Start scheduling nominally-daily fstrim operations MountServiceIdler.scheduleIdlePass(mContext); @@ -793,7 +803,7 @@ class MountService extends IMountService.Stub } } - 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub } private void handleDaemonConnected() { - synchronized (mLock) { - resetIfReadyAndConnectedLocked(); - } + resetIfReadyAndConnected(); /* * Now that we've done our initialization, release @@ -1428,6 +1440,7 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub // 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub } writeSettingsLocked(); - resetIfReadyAndConnectedLocked(); + mHandler.obtainMessage(H_RESET).sendToTarget(); } } @@ -1878,7 +1894,7 @@ class MountService extends IMountService.Stub } writeSettingsLocked(); - resetIfReadyAndConnectedLocked(); + mHandler.obtainMessage(H_RESET).sendToTarget(); } } @@ -1915,7 +1931,7 @@ class MountService extends IMountService.Stub 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 519a2a3f4f88..e6b6074abf9c 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.LocalLog; 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo private LocalLog mLocalLog; private volatile boolean mDebug = false; + private volatile Object mWarnIfHeld; private final ResponseQueue mResponseQueue; @@ -107,6 +109,15 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo */ 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 a0d305cdf2c6..3f0664defbf9 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.HandlerThread; 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 @@ public class NetworkTimeUpdateService { 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 @@ public class NetworkTimeUpdateService { 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 @@ public class NetworkTimeUpdateService { 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 df6b1d63bee2..befaaef54e49 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 2d7298bb31a4..546db3e2d68a 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 @@ package com.android.server.am; 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.END_DOCUMENT; 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.Handler; 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 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 @@ public final class ActivityManagerService extends ActivityManagerNative final ActivityThread mSystemThread; - private UserManagerService mUserManager; - private final class AppDeathRecipient implements IBinder.DeathRecipient { final ProcessRecord mApp; final int mPid; @@ -1400,7 +1404,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative @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) { @@ -8418,7 +8422,7 @@ public final class ActivityManagerService extends ActivityManagerNative @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; @@ -8436,7 +8440,7 @@ public final class ActivityManagerService extends ActivityManagerNative final Set<Integer> includedUsers; if (includeProfiles) { - includedUsers = getProfileIdsLocked(userId); + includedUsers = mUserController.getProfileIds(userId); } else { includedUsers = new HashSet<>(); } @@ -8654,12 +8658,9 @@ public final class ActivityManagerService extends ActivityManagerNative // - 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; @@ -9461,16 +9462,15 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ -9809,7 +9809,7 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 " @@ -9995,8 +9995,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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); } @@ -10308,7 +10308,7 @@ public final class ActivityManagerService extends ActivityManagerNative long ident = 0; boolean clearedIdentity = false; synchronized (this) { - userId = unsafeConvertIncomingUserLocked(userId); + userId = mUserController.unsafeConvertIncomingUserLocked(userId); } if (canClearIdentity(callingPid, callingUid, userId)) { clearedIdentity = true; @@ -10978,7 +10978,7 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean isAssistDataAllowedOnCurrentActivity() { int userId; synchronized (this) { - userId = mUserController.mCurrentUserId; + userId = mUserController.getCurrentUserIdLocked(); ActivityRecord activity = getFocusedStack().topActivity(); if (activity == null) { return false; @@ -11893,7 +11893,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // 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; } @@ -11923,8 +11923,7 @@ public final class ActivityManagerService extends ActivityManagerNative mUserController.updateCurrentProfileIdsLocked(); mRecentTasks.clear(); - mRecentTasks.addAll(mTaskPersister.restoreTasksLocked( - getUserManagerLocked().getUserIds())); + mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds())); mRecentTasks.cleanupLocked(UserHandle.USER_ALL); mTaskPersister.startPersisting(); @@ -12029,7 +12028,7 @@ public final class ActivityManagerService extends ActivityManagerNative loadResourcesOnSystemReady(); final int currentUserId; synchronized (this) { - currentUserId = mUserController.mCurrentUserId; + currentUserId = mUserController.getCurrentUserIdLocked(); readGrantedUriPermissionsLocked(); } @@ -12113,7 +12112,7 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(ident); } mStackSupervisor.resumeTopActivitiesLocked(); - sendUserSwitchBroadcastsLocked(-1, currentUserId); + mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId); } } @@ -12313,7 +12312,7 @@ public final class ActivityManagerService extends ActivityManagerNative // 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); @@ -16034,97 +16033,10 @@ public final class ActivityManagerService extends ActivityManagerNative @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; @@ -16437,8 +16349,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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) { @@ -16628,8 +16540,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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() @@ -16718,14 +16630,14 @@ public final class ActivityManagerService extends ActivityManagerNative 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())) { @@ -17030,9 +16942,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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; } @@ -17250,7 +17161,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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) { @@ -17334,7 +17245,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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()) { @@ -17600,10 +17511,11 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } } + void updateUserConfigurationLocked() { Configuration configuration = new Configuration(mConfiguration); Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration, - mUserController.mCurrentUserId); + mUserController.getCurrentUserIdLocked()); updateConfigurationLocked(configuration, null, false); } @@ -17652,7 +17564,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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); @@ -18921,7 +18833,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - 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) { @@ -19025,7 +18938,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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) { @@ -19036,14 +18949,14 @@ public final class ActivityManagerService extends ActivityManagerNative 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) { @@ -19054,6 +18967,11 @@ public final class ActivityManagerService extends ActivityManagerNative } 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) { @@ -19136,7 +19054,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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); @@ -19148,7 +19066,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - 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 = " @@ -19165,19 +19083,20 @@ public final class ActivityManagerService extends ActivityManagerNative 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++) { @@ -19187,6 +19106,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } app.reportedInteraction = isInteraction; + if (!isInteraction) { + app.interactionEventTime = 0; + } } private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { @@ -19209,7 +19131,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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, @@ -19301,6 +19223,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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(); @@ -19429,7 +19352,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - applyOomAdjLocked(app, true, now); + applyOomAdjLocked(app, true, now, nowElapsed); // Count the number of process types. switch (app.curProcState) { @@ -19858,7 +19781,7 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 { @@ -20019,31 +19942,12 @@ public final class ActivityManagerService extends ActivityManagerNative 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; @@ -20053,68 +19957,13 @@ public final class ActivityManagerService extends ActivityManagerNative 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), @@ -20148,20 +19997,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } synchronized (this) { - return isUserRunningLocked(userId, orStopped); - } - } - - boolean isUserRunningLocked(int userId, boolean orStopped) { - UserState state = mUserController.getStartedUserState(userId); - if (state == null) { - return false; - } - if (orStopped) { - return true; + return mUserController.isUserRunningLocked(userId, orStopped); } - return state.mState != UserState.STATE_STOPPING - && state.mState != UserState.STATE_SHUTDOWN; } @Override @@ -20190,19 +20027,6 @@ public final class ActivityManagerService extends ActivityManagerNative 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 b5f424d34adf..eb79ae7fbfbf 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 com.android.server.wm.WindowManagerService; 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { } } - 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 @@ final class ActivityStack { 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; } @@ -2797,8 +2789,7 @@ final class ActivityStack { 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. diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 1cd71a1cb61a..0afc715d3500 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 @@ package com.android.server.am; 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 static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; 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; @@ -554,8 +564,8 @@ public final class ActivityStackSupervisor implements DisplayListener { * @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(); @@ -1820,8 +1830,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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; @@ -2899,15 +2908,14 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 +3033,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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 +3054,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 +3198,7 @@ public final class ActivityStackSupervisor implements DisplayListener { * 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 +3283,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 +4671,7 @@ public final class ActivityStackSupervisor implements DisplayListener { @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 +4697,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 6ed880e0a2a5..8039072dccda 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 @@ final class PendingIntentRecord extends IIntentSender.Stub { 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 08203c55b5d9..b77eec8adf0e 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 @@ final class ProcessRecord { 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 @@ final class ProcessRecord { 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 edd16ef25780..862a9739fe05 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 @@ class RecentTasks extends ArrayList<TaskRecord> { 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 120b40c73c13..5fd213f96c66 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 static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; 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 @@ final class TaskRecord { 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 @@ final class TaskRecord { || 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 ff74d836e1ba..408548925175 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 @@ package com.android.server.am; 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 static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_M 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.content.pm.UserInfo; 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 com.android.server.pm.UserManagerService; 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 @@ final class UserController { 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 @@ final class UserController { // 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 @@ final class UserController { /** * Registered observers of the user switching mechanics. */ - final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers + private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers = new RemoteCallbackList<>(); /** @@ -114,6 +128,8 @@ final class UserController { */ Object mCurUserSwitchCallback; + private volatile UserManagerService mUserManager; + UserController(ActivityManagerService service) { mService = service; mHandler = mService.mHandler; @@ -176,8 +192,7 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { || 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 @@ final class UserController { 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 @@ final class UserController { } } - 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { 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 @@ final class UserController { mUserSwitchObservers.unregister(observer); } - UserState getStartedUserState(int userId) { + UserState getStartedUserStateLocked(int userId) { return mStartedUsers.get(userId); } @@ -746,9 +911,8 @@ final class UserController { 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 @@ final class UserController { * 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 @@ final class UserController { 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 @@ final class UserController { 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,13 +978,52 @@ final class UserController { UserInfo getCurrentUserLocked() { int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId; - return getUserManagerLocked().getUserInfo(userId); + return getUserInfo(userId); } - int getCurrentUserIdLocked() { + int getCurrentOrTargetUserIdLocked() { return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId; } + int getCurrentUserIdLocked() { + 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) { synchronized (mUserProfileGroupIdsSelfLocked) { int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId, @@ -824,6 +1039,10 @@ final class UserController { 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 @@ final class UserController { } } } - } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 5a66f4a74d99..28b4096c8e16 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 @@ final class UserSwitchingDialog extends AlertDialog 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 66e731a29dde..d89f47cc622d 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 fe5c2da4c6d1..2d768d8b2aae 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 @@ class AppIdleMonitor extends AppIdleStateChangeListener { } } - 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 15e539314a6e..82e0eafe6bea 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 @@ public class SyncManager { 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 02d4f4034244..6fc02f6eba16 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 @@ public class AppIdleController extends StateController { 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 @@ public class AppIdleController extends StateController { 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 17ae6dce50d9..41aea040cdc5 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index d8676167497f..150c84932aca 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 @@ public final class Installer extends SystemService { 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 22bedc3c2850..08f9bd5be167 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.ParcelFileDescriptor; 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 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { } @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 @@ public class PackageManagerService extends IPackageManager.Stub { */ 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 000000000000..c259ac2b2fec --- /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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 5d0e83d0fa15..c41d49359a66 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.Activity; 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,9 @@ import android.os.ParcelFileDescriptor; 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; @@ -92,13 +95,15 @@ import libcore.io.IoUtils; * 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"; @@ -160,15 +165,17 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -179,7 +186,7 @@ public class UserManagerService extends IUserManager.Stub { * 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<>(); @@ -194,20 +201,29 @@ public class UserManagerService extends IUserManager.Stub { * 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; @@ -267,7 +283,7 @@ public class UserManagerService extends IUserManager.Stub { -1, -1); mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); initDefaultGuestRestrictions(); - readUserListLocked(); + readUserListLILP(); sInstance = this; } } @@ -278,21 +294,23 @@ public class UserManagerService extends IUserManager.Stub { 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 + ")"); + removeUserStateLILP(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 + ")"); - removeUserStateLocked(ui.id); } } } @@ -312,7 +330,7 @@ public class UserManagerService extends IUserManager.Stub { @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); @@ -327,7 +345,7 @@ public class UserManagerService extends IUserManager.Stub { @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++) { @@ -350,8 +368,8 @@ public class UserManagerService extends IUserManager.Stub { } final long ident = Binder.clearCallingIdentity(); try { - synchronized (mPackagesLock) { - return getProfilesLocked(userId, enabledOnly); + synchronized (mUsersLock) { + return getProfilesLU(userId, enabledOnly); } } finally { Binder.restoreCallingIdentity(ident); @@ -359,8 +377,8 @@ public class UserManagerService extends IUserManager.Stub { } /** 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 @@ -387,8 +405,8 @@ public class UserManagerService extends IUserManager.Stub { public int getCredentialOwnerProfile(int userHandle) { checkManageUsersPermission("get the credential owner"); if (!"file".equals(SystemProperties.get("ro.crypto.type", "none"))) { - synchronized (mPackagesLock) { - UserInfo profileParent = getProfileParentLocked(userHandle); + synchronized (mUsersLock) { + UserInfo profileParent = getProfileParentLU(userHandle); if (profileParent != null) { return profileParent.id; } @@ -403,32 +421,35 @@ public class UserManagerService extends IUserManager.Stub { 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; - } - UserInfo otherUserInfo = getUserInfoLocked(otherUserId); - if (otherUserInfo == null || otherUserInfo.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; } - 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; } @@ -436,11 +457,11 @@ public class UserManagerService extends IUserManager.Stub { 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); @@ -450,10 +471,13 @@ public class UserManagerService extends IUserManager.Stub { 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); } } } @@ -461,23 +485,23 @@ public class UserManagerService extends IUserManager.Stub { @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; } @@ -494,7 +518,7 @@ public class UserManagerService extends IUserManager.Stub { /* * 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)) { @@ -504,11 +528,19 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -516,14 +548,14 @@ public class UserManagerService extends IUserManager.Stub { 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; } } @@ -538,13 +570,13 @@ public class UserManagerService extends IUserManager.Stub { 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 { @@ -563,12 +595,12 @@ public class UserManagerService extends IUserManager.Stub { 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"); @@ -591,13 +623,14 @@ public class UserManagerService extends IUserManager.Stub { 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); } } } @@ -616,6 +649,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public Bundle getDefaultGuestRestrictions() { checkManageUsersPermission("getDefaultGuestRestrictions"); + // TODO Switch to mGuestRestrictions for locking synchronized (mPackagesLock) { return new Bundle(mGuestRestrictions); } @@ -624,15 +658,17 @@ public class UserManagerService extends IUserManager.Stub { @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); @@ -648,7 +684,7 @@ public class UserManagerService extends IUserManager.Stub { } @GuardedBy("mRestrictionsLock") - private void invalidateEffectiveUserRestrictionsRL(int userId) { + private void invalidateEffectiveUserRestrictionsLR(int userId) { if (DBG) { Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId); } @@ -659,7 +695,7 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mRestrictionsLock) { Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId); if (restrictions == null) { - restrictions = computeEffectiveUserRestrictionsRL(userId); + restrictions = computeEffectiveUserRestrictionsLR(userId); mCachedEffectiveUserRestrictions.put(userId, restrictions); } return restrictions; @@ -705,7 +741,7 @@ public class UserManagerService extends IUserManager.Stub { UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId)); newRestrictions.putBoolean(key, value); - updateUserRestrictionsInternalRL(newRestrictions, userId); + updateUserRestrictionsInternalLR(newRestrictions, userId); } } @@ -721,14 +757,12 @@ public class UserManagerService extends IUserManager.Stub { * @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. @@ -738,15 +772,22 @@ public class UserManagerService extends IUserManager.Stub { 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); @@ -756,20 +797,22 @@ public class UserManagerService extends IUserManager.Stub { 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() { @@ -790,7 +833,7 @@ public class UserManagerService extends IUserManager.Stub { // 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]); } } } @@ -801,8 +844,12 @@ public class UserManagerService extends IUserManager.Stub { /** * 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 @@ -820,18 +867,18 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -870,7 +917,7 @@ public class UserManagerService extends IUserManager.Stub { } } - 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); @@ -904,18 +951,14 @@ public class UserManagerService extends IUserManager.Stub { * @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; @@ -932,7 +975,7 @@ public class UserManagerService extends IUserManager.Stub { if (type != XmlPullParser.START_TAG) { Slog.e(LOG_TAG, "Unable to read user list"); - fallbackToSingleUserLocked(); + fallbackToSingleUserLILP(); return; } @@ -953,12 +996,14 @@ public class UserManagerService extends IUserManager.Stub { 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)) { @@ -975,12 +1020,12 @@ public class UserManagerService extends IUserManager.Stub { } } } - updateUserIdsLocked(); - upgradeIfNecessaryLocked(); + updateUserIds(); + upgradeIfNecessaryLILP(); } catch (IOException ioe) { - fallbackToSingleUserLocked(); + fallbackToSingleUserLILP(); } catch (XmlPullParserException pe) { - fallbackToSingleUserLocked(); + fallbackToSingleUserLILP(); } finally { if (fis != null) { try { @@ -994,24 +1039,24 @@ public class UserManagerService extends IUserManager.Stub { /** * 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; } @@ -1028,13 +1073,15 @@ public class UserManagerService extends IUserManager.Stub { 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; @@ -1045,11 +1092,11 @@ public class UserManagerService extends IUserManager.Stub { + 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. @@ -1060,7 +1107,9 @@ public class UserManagerService extends IUserManager.Stub { 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; @@ -1069,14 +1118,14 @@ public class UserManagerService extends IUserManager.Stub { 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); @@ -1090,7 +1139,7 @@ public class UserManagerService extends IUserManager.Stub { * <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 { @@ -1156,7 +1205,8 @@ public class UserManagerService extends IUserManager.Stub { * <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 { @@ -1177,11 +1227,17 @@ public class UserManagerService extends IUserManager.Stub { 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); } @@ -1195,7 +1251,7 @@ public class UserManagerService extends IUserManager.Stub { } } - private UserInfo readUserLocked(int id) { + private UserInfo readUserLILP(int id) { int flags = 0; int serialNumber = id; String name = null; @@ -1389,20 +1445,22 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -1436,7 +1494,7 @@ public class UserManagerService extends IUserManager.Stub { flags |= UserInfo.FLAG_ADMIN; } } - userId = getNextAvailableIdLocked(); + userId = getNextAvailableId(); userInfo = new UserInfo(userId, name, null, flags); userInfo.serialNumber = mNextSerialNumber++; long now = System.currentTimeMillis(); @@ -1444,12 +1502,12 @@ public class UserManagerService extends IUserManager.Stub { 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) { @@ -1458,7 +1516,7 @@ public class UserManagerService extends IUserManager.Stub { } if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) { parent.restrictedProfileParentId = parent.id; - scheduleWriteUserLocked(parent); + scheduleWriteUserLP(parent); } userInfo.restrictedProfileParentId = parent.restrictedProfileParentId; } @@ -1477,8 +1535,8 @@ public class UserManagerService extends IUserManager.Stub { } mPm.createNewUserLILPw(userId); userInfo.partial = false; - scheduleWriteUserLocked(userInfo); - updateUserIdsLocked(); + scheduleWriteUserLP(userInfo); + updateUserIds(); Bundle restrictions = new Bundle(); synchronized (mRestrictionsLock) { mBaseUserRestrictions.append(userId, restrictions); @@ -1521,12 +1579,14 @@ public class UserManagerService extends IUserManager.Stub { * 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; @@ -1550,9 +1610,11 @@ public class UserManagerService extends IUserManager.Stub { 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; @@ -1566,7 +1628,7 @@ public class UserManagerService extends IUserManager.Stub { // 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); @@ -1596,15 +1658,17 @@ public class UserManagerService extends IUserManager.Stub { 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); @@ -1618,7 +1682,7 @@ public class UserManagerService extends IUserManager.Stub { // 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 @@ -1676,7 +1740,7 @@ public class UserManagerService extends IUserManager.Stub { .onUserRemoved(userHandle); synchronized (mInstallLock) { synchronized (mPackagesLock) { - removeUserStateLocked(userHandle); + removeUserStateLILP(userHandle); } } } @@ -1690,20 +1754,22 @@ public class UserManagerService extends IUserManager.Stub { } } - 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)); } @@ -1739,7 +1805,7 @@ public class UserManagerService extends IUserManager.Stub { } synchronized (mPackagesLock) { // Read the restrictions from XML - return readApplicationRestrictionsLocked(packageName, userId); + return readApplicationRestrictionsLP(packageName, userId); } } @@ -1752,7 +1818,7 @@ public class UserManagerService extends IUserManager.Stub { cleanAppRestrictionsForPackage(packageName, userId); } else { // Write the restrictions to XML - writeApplicationRestrictionsLocked(packageName, restrictions, userId); + writeApplicationRestrictionsLP(packageName, restrictions, userId); } } @@ -1800,16 +1866,15 @@ public class UserManagerService extends IUserManager.Stub { } } - 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()) { @@ -1892,17 +1957,16 @@ public class UserManagerService extends IUserManager.Stub { 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(); @@ -1972,17 +2036,17 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ -1994,13 +2058,13 @@ public class UserManagerService extends IUserManager.Stub { 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); } } } @@ -2014,22 +2078,24 @@ public class UserManagerService extends IUserManager.Stub { /** * 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; } /** @@ -2038,7 +2104,7 @@ public class UserManagerService extends IUserManager.Stub { */ 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); @@ -2046,7 +2112,7 @@ public class UserManagerService extends IUserManager.Stub { } if (now > EPOCH_PLUS_30_YEARS) { user.lastLoggedInTime = now; - scheduleWriteUserLocked(user); + scheduleWriteUserLP(user); } } } @@ -2057,8 +2123,8 @@ public class UserManagerService extends IUserManager.Stub { * 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)) { @@ -2150,6 +2216,45 @@ public class UserManagerService extends IUserManager.Stub { } @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) { @@ -2164,38 +2269,49 @@ public class UserManagerService extends IUserManager.Stub { 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); - } - 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); + 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.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:"); @@ -2212,9 +2328,9 @@ public class UserManagerService extends IUserManager.Stub { 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); } } } @@ -2238,14 +2354,14 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ -2260,17 +2376,35 @@ public class UserManagerService extends IUserManager.Stub { 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 23e3b35ae3da..28df9f673ed0 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 @@ package com.android.server.pm; 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.io.PrintWriter; import java.util.Set; public class UserRestrictionsUtils { + private static final String TAG = "UserRestrictionsUtils"; + private UserRestrictionsUtils() { } @@ -115,6 +126,118 @@ public class UserRestrictionsUtils { } } + /** + * 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 b38b9ceb0712..209751ee4bcd 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 d4c5f8716c0e..ac79b36f7005 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 @@ public class WebViewUpdateService extends SystemService { private boolean mRelroReady32Bit = false; private boolean mRelroReady64Bit = false; + private String oldWebViewPackageName = null; + private BroadcastReceiver mWebViewUpdatedReceiver; public WebViewUpdateService(Context context) { @@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService { 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index fab8ee55b22c..d6f807eb0ab8 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; @@ -311,7 +312,7 @@ class DisplayContent { 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(); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 04cba81726ec..eafc3c6beccf 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 1f62bc15024e..c47c377f4cb8 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 @@ final class Session extends IWindowSession.Stub 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 d4d99af9675d..dbf13fefef9b 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; @@ -245,7 +244,7 @@ class Task implements DimLayer.DimLayerUser { private boolean useCurrentBounds() { final DisplayContent displayContent = mStack.getDisplayContent(); if (mFullscreen - || mStack.allowTaskResize() + || !StackId.isTaskResizeableByDockedStack(mStack.mStackId) || displayContent == null || displayContent.getDockedStackLocked() != null) { return true; diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 227b3f01fac2..aae3bd24439f 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 @@ package com.android.server.wm; 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 df664bd271bd..95e23917b7e3 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 @@ public class TaskStack implements DimLayer.DimLayerUser { } } - 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. @@ -203,8 +196,7 @@ public class TaskStack implements DimLayer.DimLayerUser { /** 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 @@ public class TaskStack implements DimLayer.DimLayerUser { 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 @@ public class TaskStack implements DimLayer.DimLayerUser { } 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 @@ public class TaskStack implements DimLayer.DimLayerUser { 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 7f2f2cda8128..4a9d8cbdf006 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 bcfc8f22bf15..230e81b52e78 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; @@ -2468,6 +2468,54 @@ public class WindowManagerService extends IWindowManager.Stub } } + 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, diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5bc329e16e65..c1fa78ac8ce0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1999,4 +1999,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { } 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 aa242f1131d3..8dddbd1c93c3 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 { diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp index 06b9bc3c01ec..e12a01661a44 100644 --- a/services/core/jni/com_android_server_UsbMidiDevice.cpp +++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp @@ -43,12 +43,26 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */ 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 b4c8f966ba71..aea2ecf68858 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + // 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (removed) { validatePasswordOwnerLocked(policy); - syncDeviceCapabilitiesLocked(policy); saveSettingsLocked(policy.mUserHandle); } @@ -1061,6 +1061,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + if (removed) { + synchronized (mUserManagerInternal.getUserRestrictionsLock()) { + synchronized (DevicePolicyManagerService.this) { + mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle); + } + } + } } /** @@ -1682,7 +1689,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mAdminList.remove(admin); policy.mAdminMap.remove(adminReceiver); validatePasswordOwnerLocked(policy); - syncDeviceCapabilitiesLocked(policy); if (doProxyCleanup) { resetGlobalProxyLocked(getUserData(userHandle)); } @@ -1709,6 +1715,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateMaximumTimeToLockLocked(policy); policy.mRemovingAdmins.remove(adminReceiver); } + synchronized (mUserManagerInternal.getUserRestrictionsLock()) { + synchronized (DevicePolicyManagerService.this) { + mUserManagerInternal.updateEffectiveUserRestrictionsLR( + userHandle); + } + } } }); } @@ -2022,7 +2034,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } validatePasswordOwnerLocked(policy); - syncDeviceCapabilitiesLocked(policy); updateMaximumTimeToLockLocked(policy); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); if (policy.mStatusBarDisabled) { @@ -2089,31 +2100,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - /** - * 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ap.disabledKeyguardFeatures = which; saveSettingsLocked(userHandle); } - syncDeviceCapabilitiesLocked(getUserData(userHandle)); } } @@ -4657,7 +4650,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES); ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args)); saveSettingsLocked(userHandle); - syncDeviceCapabilitiesLocked(getUserData(userHandle)); } } @@ -5602,6 +5594,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + // 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 eb7eb15a491a..9e701385d59d 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 class UserManagerServiceTest extends AndroidTestCase { 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 13e600ef30e7..013154b39242 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements * 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 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements } @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 e11c8d3acc43..d1d6e0dcdece 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -332,9 +332,24 @@ public class SubscriptionInfo implements Parcelable { 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 861a37990629..5f84e0ce353a 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.java +++ b/telephony/java/com/android/ims/ImsCallProfile.java @@ -188,6 +188,20 @@ public class ImsCallProfile implements Parcelable { 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 b4e0c707d3f3..4771b6cfc750 100644 --- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java +++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java @@ -24,6 +24,7 @@ import android.app.Activity; 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 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC 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 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC return score; } + public boolean isAndromeda() { + return mAndromeda; + } + @Override public void onClick(View view) { if (view == mMeasureCompositionButton) { @@ -247,6 +250,9 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC 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 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC } 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 3f04888da637..388f91a2c3fb 100644 --- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java +++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java @@ -17,6 +17,7 @@ package android.surfacecomposition; 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 @@ public class SurfaceCompositionTest extends 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 @@ public class SurfaceCompositionTest extends @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 @@ public class SurfaceCompositionTest extends 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 @@ public class SurfaceCompositionTest extends 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/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 007d07553e80..498be5afe852 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -513,4 +513,8 @@ public class IWindowManagerImpl implements IWindowManager { // 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 299790765e39..11bd15d780fc 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 @@ public final class BridgeWindowSession implements IWindowSession { } @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. } |