diff options
143 files changed, 6953 insertions, 4280 deletions
diff --git a/api/current.txt b/api/current.txt index 96ecad617a91..cca8293486e3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -332,6 +332,7 @@ package android { field public static final int calendarTextColor = 16843931; // 0x101049b field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d + field public static final int canControlMagnification = 16844040; // 0x1010508 field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8 field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9 field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7 @@ -2619,6 +2620,7 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); + method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController(); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); @@ -2656,6 +2658,23 @@ package android.accessibilityservice { field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice"; } + public static final class AccessibilityService.MagnificationController { + method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener); + method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler); + method public float getCenterX(); + method public float getCenterY(); + method public android.graphics.Region getMagnifiedRegion(); + method public float getScale(); + method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener); + method public boolean reset(boolean); + method public boolean setCenter(float, float, boolean); + method public boolean setScale(float, boolean); + } + + public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener { + method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float); + } + public class AccessibilityServiceInfo implements android.os.Parcelable { ctor public AccessibilityServiceInfo(); method public static java.lang.String capabilityToString(int); @@ -2670,6 +2689,7 @@ package android.accessibilityservice { method public java.lang.String getSettingsActivityName(); method public java.lang.String loadDescription(android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10 field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4 field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8 field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2 @@ -9920,6 +9940,7 @@ package android.content.res { public static class Resources.NotFoundException extends java.lang.RuntimeException { ctor public Resources.NotFoundException(); ctor public Resources.NotFoundException(java.lang.String); + ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception); } public final class Resources.Theme { @@ -17776,6 +17797,7 @@ package android.media.session { package android.media.tv { public final class TvContentRating { + method public final boolean contains(android.media.tv.TvContentRating); method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...); method public java.lang.String flattenToString(); method public java.lang.String getDomain(); diff --git a/api/system-current.txt b/api/system-current.txt index 0605851f058d..3946d4abeae3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -424,6 +424,7 @@ package android { field public static final int calendarTextColor = 16843931; // 0x101049b field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d + field public static final int canControlMagnification = 16844040; // 0x1010508 field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8 field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9 field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7 @@ -2718,6 +2719,7 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); + method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController(); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); @@ -2755,6 +2757,23 @@ package android.accessibilityservice { field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice"; } + public static final class AccessibilityService.MagnificationController { + method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener); + method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler); + method public float getCenterX(); + method public float getCenterY(); + method public android.graphics.Region getMagnifiedRegion(); + method public float getScale(); + method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener); + method public boolean reset(boolean); + method public boolean setCenter(float, float, boolean); + method public boolean setScale(float, boolean); + } + + public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener { + method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float); + } + public class AccessibilityServiceInfo implements android.os.Parcelable { ctor public AccessibilityServiceInfo(); method public static java.lang.String capabilityToString(int); @@ -2769,6 +2788,7 @@ package android.accessibilityservice { method public java.lang.String getSettingsActivityName(); method public java.lang.String loadDescription(android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10 field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4 field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8 field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2 @@ -10261,6 +10281,7 @@ package android.content.res { public static class Resources.NotFoundException extends java.lang.RuntimeException { ctor public Resources.NotFoundException(); ctor public Resources.NotFoundException(java.lang.String); + ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception); } public final class Resources.Theme { @@ -21494,10 +21515,13 @@ package android.net.wifi { method public void writeToParcel(android.os.Parcel, int); field public int band; field public android.net.wifi.WifiScanner.ChannelSpec[] channels; + field public int exponent; + field public int maxPeriodInMs; field public int maxScansToCache; field public int numBssidsPerScan; field public int periodInMs; field public int reportEvents; + field public int stepCount; } public static abstract interface WifiScanner.WifiChangeListener implements android.net.wifi.WifiScanner.ActionListener { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 7f33cb5df1a3..62e0919a3d84 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -55,6 +55,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; +import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; @@ -72,6 +73,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; +import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; @@ -117,7 +119,8 @@ public class Am extends BaseCommand { @Override public void onShowUsage(PrintStream out) { - out.println( + PrintWriter pw = new PrintWriter(out); + pw.println( "usage: am [subcommand] [options]\n" + "usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" + " [--sampling INTERVAL] [-R COUNT] [-S]\n" + @@ -337,52 +340,10 @@ public class Am extends BaseCommand { "am send-trim-memory: send a memory trim event to a <PROCESS>.\n" + "\n" + "am get-current-user: returns id of the current foreground user.\n" + - "\n" + - "<INTENT> specifications include these flags and arguments:\n" + - " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" + - " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" + - " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" + - " [--esn <EXTRA_KEY> ...]\n" + - " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" + - " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" + - " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" + - " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" + - " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" + - " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]\n" + - " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" + - " (mutiple extras passed as Integer[])\n" + - " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" + - " (mutiple extras passed as List<Integer>)\n" + - " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" + - " (mutiple extras passed as Long[])\n" + - " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" + - " (mutiple extras passed as List<Long>)\n" + - " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" + - " (mutiple extras passed as Float[])\n" + - " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" + - " (mutiple extras passed as List<Float>)\n" + - " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" + - " (mutiple extras passed as String[]; to embed a comma into a string,\n" + - " escape it using \"\\,\")\n" + - " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" + - " (mutiple extras passed as List<String>; to embed a comma into a string,\n" + - " escape it using \"\\,\")\n" + - " [--grant-read-uri-permission] [--grant-write-uri-permission]\n" + - " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" + - " [--debug-log-resolution] [--exclude-stopped-packages]\n" + - " [--include-stopped-packages]\n" + - " [--activity-brought-to-front] [--activity-clear-top]\n" + - " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" + - " [--activity-launched-from-history] [--activity-multiple-task]\n" + - " [--activity-no-animation] [--activity-no-history]\n" + - " [--activity-no-user-action] [--activity-previous-is-top]\n" + - " [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" + - " [--activity-single-top] [--activity-clear-task]\n" + - " [--activity-task-on-home]\n" + - " [--receiver-registered-only] [--receiver-replace-pending]\n" + - " [--selector]\n" + - " [<URI> | <PACKAGE> | <COMPONENT>]\n" - ); + "\n" + ); + Intent.printIntentArgsHelp(pw, ""); + pw.flush(); } @Override @@ -486,10 +447,6 @@ public class Am extends BaseCommand { } private Intent makeIntent(int defUser) throws URISyntaxException { - Intent intent = new Intent(); - Intent baseIntent = intent; - boolean hasIntentInfo = false; - mStartFlags = 0; mWaitOption = false; mStopOption = false; @@ -498,316 +455,38 @@ public class Am extends BaseCommand { mSamplingInterval = 0; mAutoStop = false; mUserId = defUser; - Uri data = null; - String type = null; - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("-a")) { - intent.setAction(nextArgRequired()); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-d")) { - data = Uri.parse(nextArgRequired()); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-t")) { - type = nextArgRequired(); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-c")) { - intent.addCategory(nextArgRequired()); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-e") || opt.equals("--es")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - intent.putExtra(key, value); - } else if (opt.equals("--esn")) { - String key = nextArgRequired(); - intent.putExtra(key, (String) null); - } else if (opt.equals("--ei")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - intent.putExtra(key, Integer.decode(value)); - } else if (opt.equals("--eu")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - intent.putExtra(key, Uri.parse(value)); - } else if (opt.equals("--ecn")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - ComponentName cn = ComponentName.unflattenFromString(value); - if (cn == null) throw new IllegalArgumentException("Bad component name: " + value); - intent.putExtra(key, cn); - } else if (opt.equals("--eia")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - int[] list = new int[strings.length]; - for (int i = 0; i < strings.length; i++) { - list[i] = Integer.decode(strings[i]); - } - intent.putExtra(key, list); - } else if (opt.equals("--eial")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - ArrayList<Integer> list = new ArrayList<>(strings.length); - for (int i = 0; i < strings.length; i++) { - list.add(Integer.decode(strings[i])); - } - intent.putExtra(key, list); - } else if (opt.equals("--el")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - intent.putExtra(key, Long.valueOf(value)); - } else if (opt.equals("--ela")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - long[] list = new long[strings.length]; - for (int i = 0; i < strings.length; i++) { - list[i] = Long.valueOf(strings[i]); - } - intent.putExtra(key, list); - hasIntentInfo = true; - } else if (opt.equals("--elal")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - ArrayList<Long> list = new ArrayList<>(strings.length); - for (int i = 0; i < strings.length; i++) { - list.add(Long.valueOf(strings[i])); - } - intent.putExtra(key, list); - hasIntentInfo = true; - } else if (opt.equals("--ef")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - intent.putExtra(key, Float.valueOf(value)); - hasIntentInfo = true; - } else if (opt.equals("--efa")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - float[] list = new float[strings.length]; - for (int i = 0; i < strings.length; i++) { - list[i] = Float.valueOf(strings[i]); - } - intent.putExtra(key, list); - hasIntentInfo = true; - } else if (opt.equals("--efal")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - String[] strings = value.split(","); - ArrayList<Float> list = new ArrayList<>(strings.length); - for (int i = 0; i < strings.length; i++) { - list.add(Float.valueOf(strings[i])); - } - intent.putExtra(key, list); - hasIntentInfo = true; - } else if (opt.equals("--esa")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - // Split on commas unless they are preceeded by an escape. - // The escape character must be escaped for the string and - // again for the regex, thus four escape characters become one. - String[] strings = value.split("(?<!\\\\),"); - intent.putExtra(key, strings); - hasIntentInfo = true; - } else if (opt.equals("--esal")) { - String key = nextArgRequired(); - String value = nextArgRequired(); - // Split on commas unless they are preceeded by an escape. - // The escape character must be escaped for the string and - // again for the regex, thus four escape characters become one. - String[] strings = value.split("(?<!\\\\),"); - ArrayList<String> list = new ArrayList<>(strings.length); - for (int i = 0; i < strings.length; i++) { - list.add(strings[i]); - } - intent.putExtra(key, list); - hasIntentInfo = true; - } else if (opt.equals("--ez")) { - String key = nextArgRequired(); - String value = nextArgRequired().toLowerCase(); - // Boolean.valueOf() results in false for anything that is not "true", which is - // error-prone in shell commands - boolean arg; - if ("true".equals(value) || "t".equals(value)) { - arg = true; - } else if ("false".equals(value) || "f".equals(value)) { - arg = false; + return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() { + @Override + public boolean handleOption(String opt, ShellCommand cmd) { + if (opt.equals("-D")) { + mStartFlags |= ActivityManager.START_FLAG_DEBUG; + } else if (opt.equals("-W")) { + mWaitOption = true; + } else if (opt.equals("-P")) { + mProfileFile = nextArgRequired(); + mAutoStop = true; + } else if (opt.equals("--start-profiler")) { + mProfileFile = nextArgRequired(); + mAutoStop = false; + } else if (opt.equals("--sampling")) { + mSamplingInterval = Integer.parseInt(nextArgRequired()); + } else if (opt.equals("-R")) { + mRepeat = Integer.parseInt(nextArgRequired()); + } else if (opt.equals("-S")) { + mStopOption = true; + } else if (opt.equals("--track-allocation")) { + mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION; + } else if (opt.equals("--user")) { + mUserId = parseUserArg(nextArgRequired()); + } else if (opt.equals("--receiver-permission")) { + mReceiverPermission = nextArgRequired(); } else { - try { - arg = Integer.decode(value) != 0; - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Invalid boolean value: " + value); - } - } - - intent.putExtra(key, arg); - } else if (opt.equals("-n")) { - String str = nextArgRequired(); - ComponentName cn = ComponentName.unflattenFromString(str); - if (cn == null) throw new IllegalArgumentException("Bad component name: " + str); - intent.setComponent(cn); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-p")) { - String str = nextArgRequired(); - intent.setPackage(str); - if (intent == baseIntent) { - hasIntentInfo = true; - } - } else if (opt.equals("-f")) { - String str = nextArgRequired(); - intent.setFlags(Integer.decode(str).intValue()); - } else if (opt.equals("--grant-read-uri-permission")) { - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else if (opt.equals("--grant-write-uri-permission")) { - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } else if (opt.equals("--grant-persistable-uri-permission")) { - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - } else if (opt.equals("--grant-prefix-uri-permission")) { - intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - } else if (opt.equals("--exclude-stopped-packages")) { - intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); - } else if (opt.equals("--include-stopped-packages")) { - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - } else if (opt.equals("--debug-log-resolution")) { - intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); - } else if (opt.equals("--activity-brought-to-front")) { - intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - } else if (opt.equals("--activity-clear-top")) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - } else if (opt.equals("--activity-clear-when-task-reset")) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } else if (opt.equals("--activity-exclude-from-recents")) { - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - } else if (opt.equals("--activity-launched-from-history")) { - intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); - } else if (opt.equals("--activity-multiple-task")) { - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - } else if (opt.equals("--activity-no-animation")) { - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - } else if (opt.equals("--activity-no-history")) { - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - } else if (opt.equals("--activity-no-user-action")) { - intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); - } else if (opt.equals("--activity-previous-is-top")) { - intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); - } else if (opt.equals("--activity-reorder-to-front")) { - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - } else if (opt.equals("--activity-reset-task-if-needed")) { - intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - } else if (opt.equals("--activity-single-top")) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } else if (opt.equals("--activity-clear-task")) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - } else if (opt.equals("--activity-task-on-home")) { - intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME); - } else if (opt.equals("--receiver-registered-only")) { - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - } else if (opt.equals("--receiver-replace-pending")) { - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - } else if (opt.equals("--selector")) { - intent.setDataAndType(data, type); - intent = new Intent(); - } else if (opt.equals("-D")) { - mStartFlags |= ActivityManager.START_FLAG_DEBUG; - } else if (opt.equals("-W")) { - mWaitOption = true; - } else if (opt.equals("-P")) { - mProfileFile = nextArgRequired(); - mAutoStop = true; - } else if (opt.equals("--start-profiler")) { - mProfileFile = nextArgRequired(); - mAutoStop = false; - } else if (opt.equals("--sampling")) { - mSamplingInterval = Integer.parseInt(nextArgRequired()); - } else if (opt.equals("-R")) { - mRepeat = Integer.parseInt(nextArgRequired()); - } else if (opt.equals("-S")) { - mStopOption = true; - } else if (opt.equals("--track-allocation")) { - mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION; - } else if (opt.equals("--user")) { - mUserId = parseUserArg(nextArgRequired()); - } else if (opt.equals("--receiver-permission")) { - mReceiverPermission = nextArgRequired(); - } else { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - intent.setDataAndType(data, type); - - final boolean hasSelector = intent != baseIntent; - if (hasSelector) { - // A selector was specified; fix up. - baseIntent.setSelector(intent); - intent = baseIntent; - } - - String arg = nextArg(); - baseIntent = null; - if (arg == null) { - if (hasSelector) { - // If a selector has been specified, and no arguments - // have been supplied for the main Intent, then we can - // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't - // need to have a component name specified yet, the - // selector will take care of that. - baseIntent = new Intent(Intent.ACTION_MAIN); - baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); - } - } else if (arg.indexOf(':') >= 0) { - // The argument is a URI. Fully parse it, and use that result - // to fill in any data not specified so far. - baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME - | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE); - } else if (arg.indexOf('/') >= 0) { - // The argument is a component name. Build an Intent to launch - // it. - baseIntent = new Intent(Intent.ACTION_MAIN); - baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); - baseIntent.setComponent(ComponentName.unflattenFromString(arg)); - } else { - // Assume the argument is a package name. - baseIntent = new Intent(Intent.ACTION_MAIN); - baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); - baseIntent.setPackage(arg); - } - if (baseIntent != null) { - Bundle extras = intent.getExtras(); - intent.replaceExtras((Bundle)null); - Bundle uriExtras = baseIntent.getExtras(); - baseIntent.replaceExtras((Bundle)null); - if (intent.getAction() != null && baseIntent.getCategories() != null) { - HashSet<String> cats = new HashSet<String>(baseIntent.getCategories()); - for (String c : cats) { - baseIntent.removeCategory(c); + return false; } + return true; } - intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR); - if (extras == null) { - extras = uriExtras; - } else if (uriExtras != null) { - uriExtras.putAll(extras); - extras = uriExtras; - } - intent.replaceExtras(extras); - hasIntentInfo = true; - } - - if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied"); - return intent; + }); } private void runStartService() throws Exception { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 9d6aa132759c..273483a6db8a 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -17,14 +17,19 @@ package android.accessibilityservice; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.graphics.Region; +import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -36,7 +41,10 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.util.ArrayList; import java.util.List; +import java.util.Map.Entry; +import java.util.Set; /** * An accessibility service runs in the background and receives callbacks by the system @@ -373,6 +381,8 @@ public abstract class AccessibilityService extends Service { public void init(int connectionId, IBinder windowToken); public boolean onGesture(int gestureId); public boolean onKeyEvent(KeyEvent event); + public void onMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY); } private int mConnectionId; @@ -383,6 +393,8 @@ public abstract class AccessibilityService extends Service { private WindowManager mWindowManager; + private MagnificationController mMagnificationController; + /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -396,6 +408,20 @@ public abstract class AccessibilityService extends Service { public abstract void onInterrupt(); /** + * Dispatches service connection to internal components first, then the + * client code. + */ + private void dispatchServiceConnected() { + if (mMagnificationController != null) { + mMagnificationController.onServiceConnected(); + } + + // The client gets to handle service connection last, after we've set + // up any state upon which their code may rely. + onServiceConnected(); + } + + /** * This method is a part of the {@link AccessibilityService} lifecycle and is * called after the system has successfully bound to the service. If is * convenient to use this method for setting the {@link AccessibilityServiceInfo}. @@ -513,6 +539,385 @@ public abstract class AccessibilityService extends Service { } /** + * Returns the magnification controller, which may be used to query and + * modify the state of display magnification. + * <p> + * <strong>Note:</strong> In order to control magnification, your service + * must declare the capability by setting the + * {@link android.R.styleable#AccessibilityService_canControlMagnification} + * property in its meta-data. For more information, see + * {@link #SERVICE_META_DATA}. + * + * @return the magnification controller + */ + @NonNull + public final MagnificationController getMagnificationController() { + if (mMagnificationController == null) { + mMagnificationController = new MagnificationController(this); + } + return mMagnificationController; + } + + private void onMagnificationChanged(@NonNull Region region, float scale, + float centerX, float centerY) { + if (mMagnificationController != null) { + mMagnificationController.dispatchMagnificationChanged( + region, scale, centerX, centerY); + } + } + + /** + * Used to control and query the state of display magnification. + */ + public static final class MagnificationController { + private final AccessibilityService mService; + + /** + * Map of listeners to their handlers. Lazily created when adding the + * first magnification listener. + */ + private ArrayMap<OnMagnificationChangedListener, Handler> mListeners; + + MagnificationController(@NonNull AccessibilityService service) { + mService = service; + } + + /** + * Called when the service is connected. + */ + void onServiceConnected() { + if (mListeners != null && !mListeners.isEmpty()) { + setMagnificationCallbackEnabled(true); + } + } + + /** + * Adds the specified change listener to the list of magnification + * change listeners. The callback will occur on the service's main + * thread. + * + * @param listener the listener to add, must be non-{@code null} + */ + public void addListener(@NonNull OnMagnificationChangedListener listener) { + addListener(listener, null); + } + + /** + * Adds the specified change listener to the list of magnification + * change listeners. The callback will occur on the specified + * {@link Handler}'s thread, or on the service's main thread if the + * handler is {@code null}. + * + * @param listener the listener to add, must be non-null + * @param handler the handler on which the callback should execute, or + * {@code null} to execute on the service's main thread + */ + public void addListener(@NonNull OnMagnificationChangedListener listener, + @Nullable Handler handler) { + if (mListeners == null) { + mListeners = new ArrayMap<>(); + } + + final boolean shouldEnableCallback = mListeners.isEmpty(); + mListeners.put(listener, handler); + + if (shouldEnableCallback) { + // This may fail if the service is not connected yet, but if we + // still have listeners when it connects then we can try again. + setMagnificationCallbackEnabled(true); + } + } + + /** + * Removes all instances of the specified change listener from the list + * of magnification change listeners. + * + * @param listener the listener to remove, must be non-null + * @return {@code true} if at least one instance of the listener was + * removed + */ + public boolean removeListener(@NonNull OnMagnificationChangedListener listener) { + if (mListeners == null) { + return false; + } + + final int keyIndex = mListeners.indexOfKey(listener); + final boolean hasKey = keyIndex >= 0; + if (hasKey) { + mListeners.removeAt(keyIndex); + } + + if (hasKey && mListeners.isEmpty()) { + // We just removed the last listener, so we don't need + // callbacks from the service anymore. + setMagnificationCallbackEnabled(false); + } + + return hasKey; + } + + private void setMagnificationCallbackEnabled(boolean enabled) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + connection.setMagnificationCallbackEnabled(enabled); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } + + /** + * Dispatches magnification changes to any registered listeners. This + * should be called on the service's main thread. + */ + void dispatchMagnificationChanged(final @NonNull Region region, final float scale, + final float centerX, final float centerY) { + if (mListeners == null || mListeners.isEmpty()) { + Slog.d(LOG_TAG, "Received magnification changed " + + "callback with no listeners registered!"); + setMagnificationCallbackEnabled(false); + return; + } + + // Listeners may remove themselves. Perform a shallow copy to avoid + // concurrent modification. + final ArrayMap<OnMagnificationChangedListener, Handler> entries = + new ArrayMap<>(mListeners); + + for (int i = 0, count = entries.size(); i < count; i++) { + final OnMagnificationChangedListener listener = entries.keyAt(i); + final Handler handler = entries.valueAt(i); + if (handler != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onMagnificationChanged(MagnificationController.this, + region, scale, centerX, centerY); + } + }); + } else { + // We're already on the main thread, just run the listener. + listener.onMagnificationChanged(this, region, scale, centerX, centerY); + } + } + } + + /** + * Returns the current magnification scale. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will + * return a default value of {@code 1.0f}. + * + * @return the current magnification scale + */ + public float getScale() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getMagnificationScale(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to obtain scale", re); + } + } + return 1.0f; + } + + /** + * Returns the unscaled screen-relative X coordinate of the focal + * center of the magnified region. This is the point around which + * zooming occurs and is guaranteed to lie within the magnified + * region. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will + * return a default value of {@code 0.0f}. + * + * @return the unscaled screen-relative X coordinate of the center of + * the magnified region + */ + public float getCenterX() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getMagnificationCenterX(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to obtain center X", re); + } + } + return 0.0f; + } + + /** + * Returns the unscaled screen-relative Y coordinate of the focal + * center of the magnified region. This is the point around which + * zooming occurs and is guaranteed to lie within the magnified + * region. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will + * return a default value of {@code 0.0f}. + * + * @return the unscaled screen-relative Y coordinate of the center of + * the magnified region + */ + public float getCenterY() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getMagnificationCenterY(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to obtain center Y", re); + } + } + return 0.0f; + } + + /** + * Returns the region of the screen currently being magnified. If + * magnification is not enabled, the returned region will be empty. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will + * return an empty region. + * + * @return the screen-relative bounds of the magnified region + */ + @NonNull + public Region getMagnifiedRegion() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getMagnifiedRegion(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to obtain magnified region", re); + } + } + return Region.obtain(); + } + + /** + * Resets magnification scale and center to their default (e.g. no + * magnification) values. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will have + * no effect and return {@code false}. + * + * @param animate {@code true} to animate from the current scale and + * center or {@code false} to reset the scale and center + * immediately + * @return {@code true} on success, {@code false} on failure + */ + public boolean reset(boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.resetMagnification(animate); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to reset", re); + } + } + return false; + } + + /** + * Sets the magnification scale. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will have + * no effect and return {@code false}. + * + * @param scale the magnification scale to set, must be >= 1 and <= 5 + * @param animate {@code true} to animate from the current scale or + * {@code false} to set the scale immediately + * @return {@code true} on success, {@code false} on failure + */ + public boolean setScale(float scale, boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.setMagnificationScaleAndCenter( + scale, Float.NaN, Float.NaN, animate); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to set scale", re); + } + } + return false; + } + + /** + * Sets the center of the magnified viewport. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will have + * no effect and return {@code false}. + * + * @param centerX the unscaled screen-relative X coordinate on which to + * center the viewport + * @param centerY the unscaled screen-relative Y coordinate on which to + * center the viewport + * @param animate {@code true} to animate from the current viewport + * center or {@code false} to set the center immediately + * @return {@code true} on success, {@code false} on failure + */ + public boolean setCenter(float centerX, float centerY, boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.setMagnificationScaleAndCenter( + Float.NaN, centerX, centerY, animate); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to set center", re); + } + } + return false; + } + + /** + * Listener for changes in the state of magnification. + */ + public interface OnMagnificationChangedListener { + /** + * Called when the magnified region, scale, or center changes. + * + * @param controller the magnification controller + * @param region the new magnified region, may be empty if + * magnification is not enabled (e.g. scale is 1) + * @param scale the new scale + * @param centerX the new X coordinate around which magnification is focused + * @param centerY the new Y coordinate around which magnification is focused + */ + void onMagnificationChanged(@NonNull MagnificationController controller, + @NonNull Region region, float scale, float centerX, float centerY); + } + } + + /** * Performs a global action. Such an action can be performed * at any moment regardless of the current application or user * location in that application. For example going back, going @@ -645,7 +1050,7 @@ public abstract class AccessibilityService extends Service { return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { @Override public void onServiceConnected() { - AccessibilityService.this.onServiceConnected(); + AccessibilityService.this.dispatchServiceConnected(); } @Override @@ -678,6 +1083,12 @@ public abstract class AccessibilityService extends Service { public boolean onKeyEvent(KeyEvent event) { return AccessibilityService.this.onKeyEvent(event); } + + @Override + public void onMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY) { + AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY); + } }); } @@ -695,6 +1106,7 @@ public abstract class AccessibilityService extends Service { private static final int DO_ON_GESTURE = 4; private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5; private static final int DO_ON_KEY_EVENT = 6; + private static final int DO_ON_MAGNIFICATION_CHANGED = 7; private final HandlerCaller mCaller; @@ -741,6 +1153,18 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } + public void onMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = region; + args.arg2 = scale; + args.arg3 = centerX; + args.arg4 = centerY; + + final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args); + mCaller.sendMessage(message); + } + @Override public void executeMessage(Message message) { switch (message.what) { @@ -816,6 +1240,15 @@ public abstract class AccessibilityService extends Service { } } return; + case DO_ON_MAGNIFICATION_CHANGED: { + final SomeArgs args = (SomeArgs) message.obj; + final Region region = (Region) args.arg1; + final float scale = (float) args.arg2; + final float centerX = (float) args.arg3; + final float centerY = (float) args.arg4; + mCallback.onMagnificationChanged(region, scale, centerX, centerY); + } return; + default : Log.w(LOG_TAG, "Unknown message type " + message.what); } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 4edb0c6d1b07..2c98fef20cdd 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -104,6 +104,12 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008; + /** + * Capability: This accessibility service can control display magnification. + * @see android.R.styleable#AccessibilityService_canControlMagnification + */ + public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010; + private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos = new SparseArray<CapabilityInfo>(); static { @@ -123,6 +129,10 @@ public class AccessibilityServiceInfo implements Parcelable { new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS, R.string.capability_title_canRequestFilterKeyEvents, R.string.capability_desc_canRequestFilterKeyEvents)); + sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION, + new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION, + R.string.capability_title_canControlMagnification, + R.string.capability_desc_canControlMagnification)); } /** @@ -502,6 +512,10 @@ public class AccessibilityServiceInfo implements Parcelable { .AccessibilityService_canRequestFilterKeyEvents, false)) { mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS; } + if (asAttributes.getBoolean(com.android.internal.R.styleable + .AccessibilityService_canControlMagnification, false)) { + mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION; + } TypedValue peekedValue = asAttributes.peekValue( com.android.internal.R.styleable.AccessibilityService_description); if (peekedValue != null) { @@ -917,6 +931,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS: return "CAPABILITY_CAN_FILTER_KEY_EVENTS"; + case CAPABILITY_CAN_CONTROL_MAGNIFICATION: + return "CAPABILITY_CAN_CONTROL_MAGNIFICATION"; default: return "UNKNOWN"; } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 8b503ddad947..15666bf03ae8 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -17,6 +17,7 @@ package android.accessibilityservice; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.graphics.Region; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityWindowInfo; import android.view.KeyEvent; @@ -39,4 +40,6 @@ import android.view.KeyEvent; void clearAccessibilityCache(); void onKeyEvent(in KeyEvent event, int sequence); + + void onMagnificationChanged(in Region region, float scale, float centerX, float centerY); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 5f7a17d33bd1..6ac50bd6efbc 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -18,6 +18,7 @@ package android.accessibilityservice; import android.os.Bundle; import android.accessibilityservice.AccessibilityServiceInfo; +import android.graphics.Region; import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -63,4 +64,19 @@ interface IAccessibilityServiceConnection { boolean performGlobalAction(int action); oneway void setOnKeyEventResult(boolean handled, int sequence); + + float getMagnificationScale(); + + float getMagnificationCenterX(); + + float getMagnificationCenterY(); + + Region getMagnifiedRegion(); + + boolean resetMagnification(boolean animate); + + boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, + boolean animate); + + void setMagnificationCallbackEnabled(boolean enabled); } diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index a3b3022fc69c..aca07636e26e 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -23,10 +23,8 @@ import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; -import android.widget.Button; import android.widget.TimePicker; import android.widget.TimePicker.OnTimeChangedListener; -import android.widget.TimePicker.ValidationCallback; import com.android.internal.R; @@ -64,7 +62,7 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, * @param hourOfDay the hour that was set * @param minute the minute that was set */ - public void onTimeSet(TimePicker view, int hourOfDay, int minute); + void onTimeSet(TimePicker view, int hourOfDay, int minute); } /** @@ -115,7 +113,6 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true); - final int layoutResId = outValue.resourceId; final LayoutInflater inflater = LayoutInflater.from(themeContext); final View view = inflater.inflate(R.layout.time_picker_dialog, null); @@ -129,7 +126,6 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, mTimePicker.setCurrentHour(mInitialHourOfDay); mTimePicker.setCurrentMinute(mInitialMinute); mTimePicker.setOnTimeChangedListener(this); - mTimePicker.setValidationCallback(mValidationCallback); } @Override @@ -181,14 +177,4 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, mTimePicker.setCurrentHour(hour); mTimePicker.setCurrentMinute(minute); } - - private final ValidationCallback mValidationCallback = new ValidationCallback() { - @Override - public void onValidationChanged(boolean valid) { - final Button positive = getButton(BUTTON_POSITIVE); - if (positive != null) { - positive.setEnabled(valid); - } - } - }; } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index efed2e09733e..f7848f98b006 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -21,9 +21,11 @@ import android.accessibilityservice.AccessibilityService.IAccessibilityServiceCl import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.annotation.NonNull; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; +import android.graphics.Region; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; import android.os.Looper; @@ -1020,6 +1022,12 @@ public final class UiAutomation { public boolean onKeyEvent(KeyEvent event) { return false; } + + @Override + public void onMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY) { + /* do nothing */ + } }); } } diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 25d9aa9bae67..09a15de8778e 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -685,6 +685,48 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** + * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any + * audio to the HF unless explicitly told to. + * This method should be used in cases where the SCO channel is shared between multiple profiles + * and must be delegated by a source knowledgeable + * Note: This is an internal function and shouldn't be exposed + * + * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. + * + * @hide + */ + public void setAudioRouteAllowed(boolean allowed) { + if (VDBG) log("setAudioRouteAllowed"); + if (mService != null && isEnabled()) { + try { + mService.setAudioRouteAllowed(allowed); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + + /** + * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. + * Note: This is an internal function and shouldn't be exposed + * + * @hide + */ + public boolean getAudioRouteAllowed() { + if (VDBG) log("getAudioRouteAllowed"); + if (mService != null && isEnabled()) { + try { + return mService.getAudioRouteAllowed(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** * Check if Bluetooth SCO audio is connected. * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 0e23fada8317..0bb4088f62c9 100755 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -50,6 +50,8 @@ interface IBluetoothHeadset { boolean isAudioOn(); boolean connectAudio(); boolean disconnectAudio(); + void setAudioRouteAllowed(boolean allowed); + boolean getAudioRouteAllowed(); boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device); boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device); void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 68b77fe78673..30fe531c5186 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -18,6 +18,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.os.ResultReceiver; +import android.os.ShellCommand; import android.provider.MediaStore; import android.util.ArraySet; @@ -57,11 +58,13 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; +import java.io.PrintWriter; import java.io.Serializable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -3054,21 +3057,21 @@ public class Intent implements Parcelable, Cloneable { /** * Thermal state when the device is normal. This state is sent in the - * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}. + * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. * {@hide} */ public static final int EXTRA_THERMAL_STATE_NORMAL = 0; /** * Thermal state where the device is approaching its maximum threshold. This state is sent in - * the {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}. + * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. * {@hide} */ public static final int EXTRA_THERMAL_STATE_WARNING = 1; /** * Thermal state where the device has reached its maximum threshold. This state is sent in the - * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}. + * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}. * {@hide} */ public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2; @@ -5083,6 +5086,437 @@ public class Intent implements Parcelable, Cloneable { return intent; } + /** @hide */ + public interface CommandOptionHandler { + boolean handleOption(String opt, ShellCommand cmd); + } + + /** @hide */ + public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler) + throws URISyntaxException { + Intent intent = new Intent(); + Intent baseIntent = intent; + boolean hasIntentInfo = false; + + Uri data = null; + String type = null; + + String opt; + while ((opt=cmd.getNextOption()) != null) { + switch (opt) { + case "-a": + intent.setAction(cmd.getNextArgRequired()); + if (intent == baseIntent) { + hasIntentInfo = true; + } + break; + case "-d": + data = Uri.parse(cmd.getNextArgRequired()); + if (intent == baseIntent) { + hasIntentInfo = true; + } + break; + case "-t": + type = cmd.getNextArgRequired(); + if (intent == baseIntent) { + hasIntentInfo = true; + } + break; + case "-c": + intent.addCategory(cmd.getNextArgRequired()); + if (intent == baseIntent) { + hasIntentInfo = true; + } + break; + case "-e": + case "--es": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, value); + } + break; + case "--esn": { + String key = cmd.getNextArgRequired(); + intent.putExtra(key, (String) null); + } + break; + case "--ei": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Integer.decode(value)); + } + break; + case "--eu": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Uri.parse(value)); + } + break; + case "--ecn": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + ComponentName cn = ComponentName.unflattenFromString(value); + if (cn == null) + throw new IllegalArgumentException("Bad component name: " + value); + intent.putExtra(key, cn); + } + break; + case "--eia": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + int[] list = new int[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Integer.decode(strings[i]); + } + intent.putExtra(key, list); + } + break; + case "--eial": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + ArrayList<Integer> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(Integer.decode(strings[i])); + } + intent.putExtra(key, list); + } + break; + case "--el": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Long.valueOf(value)); + } + break; + case "--ela": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + long[] list = new long[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Long.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--elal": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + ArrayList<Long> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(Long.valueOf(strings[i])); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--ef": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Float.valueOf(value)); + hasIntentInfo = true; + } + break; + case "--efa": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + float[] list = new float[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Float.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--efal": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + ArrayList<Float> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(Float.valueOf(strings[i])); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--esa": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + // Split on commas unless they are preceeded by an escape. + // The escape character must be escaped for the string and + // again for the regex, thus four escape characters become one. + String[] strings = value.split("(?<!\\\\),"); + intent.putExtra(key, strings); + hasIntentInfo = true; + } + break; + case "--esal": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + // Split on commas unless they are preceeded by an escape. + // The escape character must be escaped for the string and + // again for the regex, thus four escape characters become one. + String[] strings = value.split("(?<!\\\\),"); + ArrayList<String> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--ez": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired().toLowerCase(); + // Boolean.valueOf() results in false for anything that is not "true", which is + // error-prone in shell commands + boolean arg; + if ("true".equals(value) || "t".equals(value)) { + arg = true; + } else if ("false".equals(value) || "f".equals(value)) { + arg = false; + } else { + try { + arg = Integer.decode(value) != 0; + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid boolean value: " + value); + } + } + + intent.putExtra(key, arg); + } + break; + case "-n": { + String str = cmd.getNextArgRequired(); + ComponentName cn = ComponentName.unflattenFromString(str); + if (cn == null) + throw new IllegalArgumentException("Bad component name: " + str); + intent.setComponent(cn); + if (intent == baseIntent) { + hasIntentInfo = true; + } + } + break; + case "-p": { + String str = cmd.getNextArgRequired(); + intent.setPackage(str); + if (intent == baseIntent) { + hasIntentInfo = true; + } + } + break; + case "-f": + String str = cmd.getNextArgRequired(); + intent.setFlags(Integer.decode(str).intValue()); + break; + case "--grant-read-uri-permission": + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + break; + case "--grant-write-uri-permission": + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + break; + case "--grant-persistable-uri-permission": + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + break; + case "--grant-prefix-uri-permission": + intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + break; + case "--exclude-stopped-packages": + intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); + break; + case "--include-stopped-packages": + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + break; + case "--debug-log-resolution": + intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); + break; + case "--activity-brought-to-front": + intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + break; + case "--activity-clear-top": + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + break; + case "--activity-clear-when-task-reset": + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + break; + case "--activity-exclude-from-recents": + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + break; + case "--activity-launched-from-history": + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + break; + case "--activity-multiple-task": + intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + break; + case "--activity-no-animation": + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + break; + case "--activity-no-history": + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + break; + case "--activity-no-user-action": + intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); + break; + case "--activity-previous-is-top": + intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); + break; + case "--activity-reorder-to-front": + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + break; + case "--activity-reset-task-if-needed": + intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + break; + case "--activity-single-top": + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + break; + case "--activity-clear-task": + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + break; + case "--activity-task-on-home": + intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME); + break; + case "--receiver-registered-only": + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + break; + case "--receiver-replace-pending": + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + break; + case "--selector": + intent.setDataAndType(data, type); + intent = new Intent(); + break; + default: + if (optionHandler != null && optionHandler.handleOption(opt, cmd)) { + // Okay, caller handled this option. + } else { + throw new IllegalArgumentException("Unknown option: " + opt); + } + break; + } + } + intent.setDataAndType(data, type); + + final boolean hasSelector = intent != baseIntent; + if (hasSelector) { + // A selector was specified; fix up. + baseIntent.setSelector(intent); + intent = baseIntent; + } + + String arg = cmd.getNextArg(); + baseIntent = null; + if (arg == null) { + if (hasSelector) { + // If a selector has been specified, and no arguments + // have been supplied for the main Intent, then we can + // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't + // need to have a component name specified yet, the + // selector will take care of that. + baseIntent = new Intent(Intent.ACTION_MAIN); + baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); + } + } else if (arg.indexOf(':') >= 0) { + // The argument is a URI. Fully parse it, and use that result + // to fill in any data not specified so far. + baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME + | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE); + } else if (arg.indexOf('/') >= 0) { + // The argument is a component name. Build an Intent to launch + // it. + baseIntent = new Intent(Intent.ACTION_MAIN); + baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); + baseIntent.setComponent(ComponentName.unflattenFromString(arg)); + } else { + // Assume the argument is a package name. + baseIntent = new Intent(Intent.ACTION_MAIN); + baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); + baseIntent.setPackage(arg); + } + if (baseIntent != null) { + Bundle extras = intent.getExtras(); + intent.replaceExtras((Bundle)null); + Bundle uriExtras = baseIntent.getExtras(); + baseIntent.replaceExtras((Bundle)null); + if (intent.getAction() != null && baseIntent.getCategories() != null) { + HashSet<String> cats = new HashSet<String>(baseIntent.getCategories()); + for (String c : cats) { + baseIntent.removeCategory(c); + } + } + intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR); + if (extras == null) { + extras = uriExtras; + } else if (uriExtras != null) { + uriExtras.putAll(extras); + extras = uriExtras; + } + intent.replaceExtras(extras); + hasIntentInfo = true; + } + + if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied"); + return intent; + } + + /** @hide */ + public static void printIntentArgsHelp(PrintWriter pw, String prefix) { + final String[] lines = new String[] { + "<INTENT> specifications include these flags and arguments:", + " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]", + " [-c <CATEGORY> [-c <CATEGORY>] ...]", + " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]", + " [--esn <EXTRA_KEY> ...]", + " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]", + " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]", + " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]", + " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]", + " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]", + " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]", + " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", + " (mutiple extras passed as Integer[])", + " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", + " (mutiple extras passed as List<Integer>)", + " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", + " (mutiple extras passed as Long[])", + " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", + " (mutiple extras passed as List<Long>)", + " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", + " (mutiple extras passed as Float[])", + " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", + " (mutiple extras passed as List<Float>)", + " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", + " (mutiple extras passed as String[]; to embed a comma into a string,", + " escape it using \"\\,\")", + " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", + " (mutiple extras passed as List<String>; to embed a comma into a string,", + " escape it using \"\\,\")", + " [--grant-read-uri-permission] [--grant-write-uri-permission]", + " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]", + " [--debug-log-resolution] [--exclude-stopped-packages]", + " [--include-stopped-packages]", + " [--activity-brought-to-front] [--activity-clear-top]", + " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]", + " [--activity-launched-from-history] [--activity-multiple-task]", + " [--activity-no-animation] [--activity-no-history]", + " [--activity-no-user-action] [--activity-previous-is-top]", + " [--activity-reorder-to-front] [--activity-reset-task-if-needed]", + " [--activity-single-top] [--activity-clear-task]", + " [--activity-task-on-home]", + " [--receiver-registered-only] [--receiver-replace-pending]", + " [--selector]", + " [<URI> | <PACKAGE> | <COMPONENT>]" + }; + for (String line : lines) { + pw.print(prefix); + pw.println(line); + } + } + /** * Retrieve the general action to be performed, such as * {@link #ACTION_VIEW}. The action describes the general way the rest of diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 9c880d3591c3..eda4136b311c 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -479,6 +479,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6; /** + * Value for {@link #privateFlags}: set to {@code true} if the application + * is AutoPlay. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7; + + /** * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants. * {@hide} */ @@ -1049,6 +1057,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * @hide */ + public boolean isAutoPlayApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0; + } + + /** + * @hide + */ @Override protected ApplicationInfo getApplicationInfo() { return this; } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 0606e35a081e..7b3dde4262f4 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -48,7 +48,6 @@ import android.icu.text.PluralRules; import android.os.Build; import android.os.Bundle; import android.os.Trace; -import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.LocaleList; @@ -68,7 +67,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; -import java.lang.ref.WeakReference; import java.util.Locale; /** @@ -120,9 +118,6 @@ public class Resources { private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>> sPreloadedColorStateLists = new LongSparseArray<>(); - private static final String CACHE_NOT_THEMED = ""; - private static final String CACHE_NULL_THEME = "null_theme"; - // Pool of TypedArrays targeted to this Resources object. final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5); @@ -130,10 +125,11 @@ public class Resources { static Resources mSystem = null; private static boolean sPreloaded; - private static int sPreloadedDensity; - // These are protected by mAccessLock. + /** Lock object used to protect access to caches and configuration. */ private final Object mAccessLock = new Object(); + + // These are protected by mAccessLock. private final Configuration mTmpConfig = new Configuration(); private final DrawableCache mDrawableCache = new DrawableCache(this); private final DrawableCache mColorDrawableCache = new DrawableCache(this); @@ -147,7 +143,12 @@ public class Resources { /** Used to inflate drawable objects from XML. */ private DrawableInflater mDrawableInflater; + /** Lock object used to protect access to {@link #mTmpValue}. */ + private final Object mTmpValueLock = new Object(); + + /** Single-item pool used to minimize TypedValue allocations. */ private TypedValue mTmpValue = new TypedValue(); + private boolean mPreloading; private int mLastCachedXmlBlockIndex = -1; @@ -249,6 +250,10 @@ public class Resources { public NotFoundException(String name) { super(name); } + + public NotFoundException(String name, Exception cause) { + super(name, cause); + } } /** @@ -621,18 +626,15 @@ public class Resources { * @see #getDimensionPixelSize */ public float getDimension(@DimenRes int id) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimension(value.data, mMetrics); } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -656,19 +658,15 @@ public class Resources { * @see #getDimensionPixelSize */ public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelOffset( - value.data, mMetrics); + return TypedValue.complexToDimensionPixelOffset(value.data, mMetrics); } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -693,19 +691,15 @@ public class Resources { * @see #getDimensionPixelOffset */ public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - value.data, mMetrics); + return TypedValue.complexToDimensionPixelSize(value.data, mMetrics); } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -727,18 +721,15 @@ public class Resources { * @throws NotFoundException Throws NotFoundException if the given ID does not exist. */ public float getFraction(@FractionRes int id, int base, int pbase) { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_FRACTION) { return TypedValue.complexToFraction(value.data, base, pbase); } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -801,24 +792,14 @@ public class Resources { * not exist. */ @Nullable - public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } else { - mTmpValue = null; - } - getValue(id, value, true); - } - final Drawable res = loadDrawable(value, id, theme); - synchronized (mAccessLock) { - if (mTmpValue == null) { - mTmpValue = value; - } + public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) + throws NotFoundException { + final TypedValue value = obtainTempTypedValue(id); + try { + return loadDrawable(value, id, theme); + } finally { + releaseTempTypedValue(value); } - return res; } /** @@ -849,7 +830,8 @@ public class Resources { */ @Deprecated @Nullable - public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException { + public Drawable getDrawableForDensity(@DrawableRes int id, int density) + throws NotFoundException { return getDrawableForDensity(id, density, null); } @@ -869,14 +851,8 @@ public class Resources { */ @Nullable public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } else { - mTmpValue = null; - } + final TypedValue value = obtainTempTypedValue(id); + try { getValueForDensity(id, density, value, true); /* @@ -893,15 +869,11 @@ public class Resources { value.density = (value.density * mMetrics.densityDpi) / density; } } - } - final Drawable res = loadDrawable(value, id, theme); - synchronized (mAccessLock) { - if (mTmpValue == null) { - mTmpValue = value; - } + return loadDrawable(value, id, theme); + } finally { + releaseTempTypedValue(value); } - return res; } /** @@ -963,33 +935,21 @@ public class Resources { */ @ColorInt public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT) { - mTmpValue = value; return value.data; } else if (value.type != TypedValue.TYPE_STRING) { - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } - mTmpValue = null; - } - final ColorStateList csl = loadColorStateList(value, id, theme); - synchronized (mAccessLock) { - if (mTmpValue == null) { - mTmpValue = value; - } + final ColorStateList csl = loadColorStateList(value, id, theme); + return csl.getDefaultColor(); + } finally { + releaseTempTypedValue(value); } - - return csl.getDefaultColor(); } /** @@ -1043,25 +1003,12 @@ public class Resources { @Nullable public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme) throws NotFoundException { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } else { - mTmpValue = null; - } - getValue(id, value, true); - } - - final ColorStateList res = loadColorStateList(value, id, theme); - synchronized (mAccessLock) { - if (mTmpValue == null) { - mTmpValue = value; - } + final TypedValue value = obtainTempTypedValue(id); + try { + return loadColorStateList(value, id, theme); + } finally { + releaseTempTypedValue(value); } - - return res; } /** @@ -1078,19 +1025,16 @@ public class Resources { * @return Returns the boolean value contained in the resource. */ public boolean getBoolean(@BoolRes int id) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type >= TypedValue.TYPE_FIRST_INT - && value.type <= TypedValue.TYPE_LAST_INT) { + && value.type <= TypedValue.TYPE_LAST_INT) { return value.data != 0; } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -1106,19 +1050,16 @@ public class Resources { * @return Returns the integer value contained in the resource. */ public int getInteger(@IntegerRes int id) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type >= TypedValue.TYPE_FIRST_INT - && value.type <= TypedValue.TYPE_LAST_INT) { + && value.type <= TypedValue.TYPE_LAST_INT) { return value.data; } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -1136,17 +1077,15 @@ public class Resources { * @hide Pending API council approval. */ public float getFloat(int id) { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_FLOAT) { return value.getFloat(); } - throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } @@ -1238,22 +1177,60 @@ public class Resources { * */ public InputStream openRawResource(@RawRes int id) throws NotFoundException { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } else { + final TypedValue value = obtainTempTypedValue(); + try { + return openRawResource(id, value); + } finally { + releaseTempTypedValue(value); + } + } + + /** + * Returns a TypedValue populated with data for the specified resource ID + * that's suitable for temporary use. The obtained TypedValue should be + * released using {@link #releaseTempTypedValue(TypedValue)}. + * + * @param id the resource ID for which data should be obtained + * @return a populated typed value suitable for temporary use + */ + private TypedValue obtainTempTypedValue(@AnyRes int id) { + final TypedValue value = obtainTempTypedValue(); + getValue(id, value, true); + return value; + } + + /** + * Returns a TypedValue suitable for temporary use. The obtained TypedValue + * should be released using {@link #releaseTempTypedValue(TypedValue)}. + * + * @return a typed value suitable for temporary use + */ + private TypedValue obtainTempTypedValue() { + TypedValue tmpValue = null; + synchronized (mTmpValueLock) { + if (mTmpValue != null) { + tmpValue = mTmpValue; mTmpValue = null; } } - InputStream res = openRawResource(id, value); - synchronized (mAccessLock) { + if (tmpValue == null) { + return new TypedValue(); + } + return tmpValue; + } + + /** + * Returns a TypedValue to the pool. After calling this method, the + * specified TypedValue should no longer be accessed. + * + * @param value the typed value to return to the pool + */ + private void releaseTempTypedValue(TypedValue value) { + synchronized (mTmpValueLock) { if (mTmpValue == null) { mTmpValue = value; } } - return res; } /** @@ -1307,32 +1284,14 @@ public class Resources { */ public AssetFileDescriptor openRawResourceFd(@RawRes int id) throws NotFoundException { - TypedValue value; - synchronized (mAccessLock) { - value = mTmpValue; - if (value == null) { - value = new TypedValue(); - } else { - mTmpValue = null; - } - getValue(id, value, true); - } + final TypedValue value = obtainTempTypedValue(id); try { - return mAssets.openNonAssetFd( - value.assetCookie, value.string.toString()); + return mAssets.openNonAssetFd(value.assetCookie, value.string.toString()); } catch (Exception e) { - NotFoundException rnf = new NotFoundException( - "File " + value.string.toString() - + " from drawable resource ID #0x" - + Integer.toHexString(id)); - rnf.initCause(e); - throw rnf; + throw new NotFoundException("File " + value.string.toString() + " from drawable " + + "resource ID #0x" + Integer.toHexString(id), e); } finally { - synchronized (mAccessLock) { - if (mTmpValue == null) { - mTmpValue = value; - } - } + releaseTempTypedValue(value); } } @@ -2015,8 +1974,8 @@ public class Resources { Build.VERSION.RESOURCES_SDK_INT); if (DEBUG_CONFIG) { - Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration - + " final compat is " + mCompatibilityInfo); + Slog.i(TAG, "**** Updating config of " + this + ": final config is " + + mConfiguration + " final compat is " + mCompatibilityInfo); } mDrawableCache.onConfigurationChange(configChanges); @@ -2402,8 +2361,7 @@ public class Resources { } sPreloaded = true; mPreloading = true; - sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE; - mConfiguration.densityDpi = sPreloadedDensity; + mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE; updateConfiguration(null, null); } } @@ -2740,19 +2698,16 @@ public class Resources { /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException { - synchronized (mAccessLock) { - TypedValue value = mTmpValue; - if (value == null) { - mTmpValue = value = new TypedValue(); - } - getValue(id, value, true); + final TypedValue value = obtainTempTypedValue(id); + try { if (value.type == TypedValue.TYPE_STRING) { return loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type); } - throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + + " type #0x" + Integer.toHexString(value.type) + " is not valid"); + } finally { + releaseTempTypedValue(value); } } diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl index f55883ae7a70..7a1a6a29ad12 100644 --- a/core/java/android/os/IDeviceIdleController.aidl +++ b/core/java/android/os/IDeviceIdleController.aidl @@ -35,4 +35,6 @@ interface IDeviceIdleController { long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason); long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason); void exitIdle(String reason); + void downloadServiceActive(IBinder token); + void downloadServiceInactive(); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 4ac361d00682..54bfca365769 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -632,9 +632,6 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) { argsForZygote.add("--enable-checkjni"); } - if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) { - argsForZygote.add("--enable-jit"); - } if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) { argsForZygote.add("--generate-debug-info"); } diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java index cad482b6bab9..6f12b625318f 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -48,19 +48,35 @@ public abstract class ShellCommand { private FastPrintWriter mErrPrintWriter; private InputStream mInputStream; - public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ResultReceiver resultReceiver) { + public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, int firstArgPos) { mTarget = target; mIn = in; mOut = out; mErr = err; mArgs = args; - mResultReceiver = resultReceiver; - mCmd = args != null && args.length > 0 ? args[0] : null; - mArgPos = 1; + mResultReceiver = null; + mCmd = null; + mArgPos = firstArgPos; mCurArgData = null; mOutPrintWriter = null; mErrPrintWriter = null; + } + + public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ResultReceiver resultReceiver) { + String cmd; + int start; + if (args != null && args.length > 0) { + cmd = args[0]; + start = 1; + } else { + cmd = null; + start = 0; + } + init(target, in, out, err, args, start); + mCmd = cmd; + mResultReceiver = resultReceiver; if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget); int res = -1; diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 95da438bc869..f946ca7ed415 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -256,6 +256,23 @@ public final class UserHandle implements Parcelable { } } + /** @hide */ + public static int parseUserArg(String arg) { + int userId; + if ("all".equals(arg)) { + userId = UserHandle.USER_ALL; + } else if ("current".equals(arg) || "cur".equals(arg)) { + userId = UserHandle.USER_CURRENT; + } else { + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Bad user number: " + arg); + } + } + return userId; + } + /** * Returns the user id of the current process * @return user id of the current process diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index d19c7c979501..2e43ffcbcc4f 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -21,8 +21,12 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.RemoteException; +import java.io.FileDescriptor; + /** * WARNING! Update IMountService.h and IMountService.cpp if you change this * file. In particular, the ordering of the methods below must match the @@ -1312,6 +1316,25 @@ public interface IMountService extends IInterface { } return _result; } + + @Override + public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + ParcelFileDescriptor _result = null; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(name); + mRemote.transact(Stub.TRANSACTION_mountAppFuse, _data, _reply, 0); + _reply.readException(); + _result = _reply.<ParcelFileDescriptor>readParcelable( + ClassLoader.getSystemClassLoader()); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } } private static final String DESCRIPTOR = "IMountService"; @@ -1439,6 +1462,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67; static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68; + static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -2056,6 +2081,14 @@ public interface IMountService extends IInterface { reply.writeInt(result ? 1 : 0); return true; } + case TRANSACTION_mountAppFuse: { + data.enforceInterface(DESCRIPTOR); + String name = data.readString(); + ParcelFileDescriptor fd = mountAppFuse(name); + reply.writeNoException(); + reply.writeParcelable(fd, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -2379,4 +2412,6 @@ public interface IMountService extends IInterface { throws RemoteException; public boolean isPerUserEncryptionEnabled() throws RemoteException; + + public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 27df46d7ee0e..2d9090b6f99a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -30,6 +30,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; @@ -293,7 +294,7 @@ public class StorageManager { /** * Constructs a StorageManager object through which an application can * can communicate with the systems mount service. - * + * * @param tgtLooper The {@link android.os.Looper} which events will be received on. * * <p>Applications can get instance of this class by calling @@ -409,7 +410,7 @@ public class StorageManager { * file matches a package ID that is owned by the calling program's UID. * That is, shared UID applications can attempt to mount any other * application's OBB that shares its UID. - * + * * @param rawPath the path to the OBB file * @param key secret used to encrypt the OBB; may be <code>null</code> if no * encryption was used on the OBB. @@ -447,7 +448,7 @@ public class StorageManager { * That is, shared UID applications can obtain access to any other * application's OBB that shares its UID. * <p> - * + * * @param rawPath path to the OBB file * @param force whether to kill any programs using this in order to unmount * it @@ -471,7 +472,7 @@ public class StorageManager { /** * Check whether an Opaque Binary Blob (OBB) is mounted or not. - * + * * @param rawPath path to OBB image * @return true if OBB is mounted; false if not mounted or on error */ @@ -491,7 +492,7 @@ public class StorageManager { * Check the mounted path of an Opaque Binary Blob (OBB) file. This will * give you the path to where you can obtain access to the internals of the * OBB. - * + * * @param rawPath path to OBB image * @return absolute path to mounted OBB image data or <code>null</code> if * not mounted or exception encountered trying to read status @@ -1049,6 +1050,15 @@ public class StorageManager { return path; } + /** {@hide} */ + public ParcelFileDescriptor mountAppFuse(String name) { + try { + return mMountService.mountAppFuse(name); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /// Consts to match the password types in cryptfs.h /** @hide */ public static final int CRYPT_TYPE_PASSWORD = 0; diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java index 0ee6714f7b5f..49242bb5aa65 100644 --- a/core/java/android/view/MagnificationSpec.java +++ b/core/java/android/view/MagnificationSpec.java @@ -28,10 +28,21 @@ import android.util.Pools.SynchronizedPool; public class MagnificationSpec implements Parcelable { private static final int MAX_POOL_SIZE = 20; private static final SynchronizedPool<MagnificationSpec> sPool = - new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE); + new SynchronizedPool<>(MAX_POOL_SIZE); + /** The magnification scaling factor. */ public float scale = 1.0f; + + /** + * The X coordinate, in unscaled screen-relative pixels, around which + * magnification is focused. + */ public float offsetX; + + /** + * The Y coordinate, in unscaled screen-relative pixels, around which + * magnification is focused. + */ public float offsetY; private MagnificationSpec() { @@ -75,6 +86,12 @@ public class MagnificationSpec implements Parcelable { offsetY = 0.0f; } + public void setTo(MagnificationSpec other) { + scale = other.scale; + offsetX = other.offsetX; + offsetY = other.offsetY; + } + @Override public int describeContents() { return 0; @@ -89,6 +106,28 @@ public class MagnificationSpec implements Parcelable { } @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final MagnificationSpec s = (MagnificationSpec) other; + return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY; + } + + @Override + public int hashCode() { + int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0); + result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0); + result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0); + return result; + } + + @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("<scale:"); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fdea1ba157f4..cd9dd97468b3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; + import android.Manifest; import android.animation.LayoutTransition; import android.app.ActivityManagerNative; @@ -37,7 +39,6 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; -import android.hardware.input.InputManager; import android.media.AudioManager; import android.os.Binder; import android.os.Build; @@ -74,7 +75,6 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.WindowCallbacks; import android.widget.Scroller; import com.android.internal.R; @@ -176,6 +176,11 @@ public final class ViewRootImpl implements ViewParent, int mViewVisibility; boolean mAppVisible = true; + // For recents to freeform transition we need to keep drawing after the app receives information + // that it became invisible. This will ignore that information and depend on the decor view + // visibility to control drawing. The decor view visibility will get adjusted when the app get + // stopped and that's when the app will stop drawing further frames. + private boolean mForceDecorViewVisibility = false; int mOrigWindowType = -1; /** Whether the window had focus during the most recent traversal. */ @@ -561,6 +566,8 @@ public final class ViewRootImpl implements ViewParent, & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } + mForceDecorViewVisibility = (mWindowAttributes.privateFlags + & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; @@ -1063,7 +1070,7 @@ public final class ViewRootImpl implements ViewParent, } int getHostVisibility() { - return mAppVisible ? mView.getVisibility() : View.GONE; + return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE; } /** @@ -1568,7 +1575,7 @@ public final class ViewRootImpl implements ViewParent, boolean insetsPending = false; int relayoutResult = 0; - boolean isViewVisible = viewVisibility == View.VISIBLE; + final boolean isViewVisible = viewVisibility == View.VISIBLE; if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) { @@ -2471,8 +2478,7 @@ public final class ViewRootImpl implements ViewParent, if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { - ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( - mSurfaceHolder); + ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder); } } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index edf4297afdea..1521f2ed07fe 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -118,8 +118,7 @@ public interface WindowManager extends ViewManager { */ public void removeViewImmediate(View view); - public static class LayoutParams extends ViewGroup.LayoutParams - implements Parcelable { + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or @@ -1149,13 +1148,21 @@ public interface WindowManager extends ViewManager { /** * Flag indicating that the x, y, width, and height members should be - * ignored (and thus their previous value preserved). For example + * ignored (and thus their previous value preserved). For example * because they are being managed externally through repositionChild. * * {@hide} */ public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000; + /** + * Flag that will make window ignore app visibility and instead depend purely on the decor + * view visibility for determining window visibility. This is used by recents to keep + * drawing after it launches an app. + * @hide + */ + public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000; + /** * Control flags that are private to the platform. diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index 3882bca043e8..89b1eb933025 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; @@ -55,9 +56,10 @@ public abstract class WindowManagerInternal { * Called when the bounds of the screen content that is magnified changed. * Note that not the entire screen is magnified. * - * @param bounds The bounds. + * @param magnifiedBounds the currently magnified region + * @param availableBounds the region available for magnification */ - public void onMagnifedBoundsChanged(Region bounds); + public void onMagnifiedBoundsChanged(Region magnifiedBounds, Region availableBounds); /** * Called when an application requests a rectangle on the screen to allow @@ -142,7 +144,7 @@ public abstract class WindowManagerInternal { * * @param callbacks The callbacks to invoke. */ - public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks); + public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks); /** * Set by the accessibility layer to specify the magnification and panning to diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 34a843925700..a53df8835b08 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -21,7 +21,6 @@ import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.MenuPresenter; import com.android.internal.view.menu.ShowableListMenu; -import com.android.internal.view.menu.SubMenuBuilder; import android.annotation.MenuRes; import android.content.Context; @@ -33,35 +32,23 @@ import android.view.View; import android.view.View.OnTouchListener; /** - * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}. - * The popup will appear below the anchor view if there is room, or above it if there is not. - * If the IME is visible the popup will not overlap it until it is touched. Touching outside - * of the popup will dismiss it. + * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a + * {@link View}. The popup will appear below the anchor view if there is room, + * or above it if there is not. If the IME is visible the popup will not + * overlap it until it is touched. Touching outside of the popup will dismiss + * it. */ -public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { +public class PopupMenu { private final Context mContext; private final MenuBuilder mMenu; private final View mAnchor; private final MenuPopupHelper mPopup; - private final boolean mShowCascadingMenus; private OnMenuItemClickListener mMenuItemClickListener; private OnDismissListener mDismissListener; private OnTouchListener mDragListener; /** - * Callback interface used to notify the application that the menu has closed. - */ - public interface OnDismissListener { - /** - * Called when the associated menu has been dismissed. - * - * @param menu The PopupMenu that was dismissed. - */ - public void onDismiss(PopupMenu menu); - } - - /** * Constructor to create a new popup menu with an anchor view. * * @param context Context the popup menu is running in, through which it @@ -108,14 +95,40 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, int popupStyleRes) { mContext = context; - mShowCascadingMenus = context.getResources().getBoolean( - com.android.internal.R.bool.config_enableCascadingSubmenus); - mMenu = new MenuBuilder(context); - mMenu.setCallback(this); mAnchor = anchor; + + mMenu = new MenuBuilder(context); + mMenu.setCallback(new MenuBuilder.Callback() { + @Override + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + if (mMenuItemClickListener != null) { + return mMenuItemClickListener.onMenuItemClick(item); + } + return false; + } + + @Override + public void onMenuModeChange(MenuBuilder menu) { + } + }); + mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes); mPopup.setGravity(gravity); - mPopup.setCallback(this); + mPopup.setCallback(new MenuPresenter.Callback() { + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mDismissListener != null) { + mDismissListener.onDismiss(PopupMenu.this); + } + } + + @Override + public boolean onOpenSubMenu(MenuBuilder subMenu) { + // The menu presenter will handle opening the submenu itself. + // Nothing to do here. + return false; + } + }); } /** @@ -125,7 +138,6 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { * the next time the popup is shown. * * @param gravity the gravity used to align the popup window - * * @see #getGravity() */ public void setGravity(int gravity) { @@ -134,7 +146,6 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { /** * @return the gravity used to align the popup window to its anchor view - * * @see #setGravity(int) */ public int getGravity() { @@ -146,8 +157,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { * to implement drag-to-open behavior. * <p> * When the listener is set on a view, touching that view and dragging - * outside of its bounds will open the popup window. Lifting will select the - * currently touched list item. + * outside of its bounds will open the popup window. Lifting will select + * the currently touched list item. * <p> * Example usage: * <pre> @@ -184,9 +195,10 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } /** - * @return the {@link Menu} associated with this popup. Populate the returned Menu with - * items before calling {@link #show()}. + * Returns the {@link Menu} associated with this popup. Populate the + * returned Menu with items before calling {@link #show()}. * + * @return the {@link Menu} associated with this popup * @see #show() * @see #getMenuInflater() */ @@ -195,9 +207,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } /** - * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the - * menu returned by {@link #getMenu()}. - * + * @return a {@link MenuInflater} that can be used to inflate menu items + * from XML into the menu returned by {@link #getMenu()} * @see #getMenu() */ public MenuInflater getMenuInflater() { @@ -205,8 +216,9 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } /** - * Inflate a menu resource into this PopupMenu. This is equivalent to calling - * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()). + * Inflate a menu resource into this PopupMenu. This is equivalent to + * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}. + * * @param menuRes Menu resource to inflate */ public void inflate(@MenuRes int menuRes) { @@ -215,6 +227,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { /** * Show the menu popup anchored to the view specified during construction. + * * @see #dismiss() */ public void show() { @@ -223,6 +236,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { /** * Dismiss the menu popup. + * * @see #show() */ public void dismiss() { @@ -230,68 +244,49 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } /** - * Set a listener that will be notified when the user selects an item from the menu. + * Sets a listener that will be notified when the user selects an item from + * the menu. * - * @param listener Listener to notify + * @param listener the listener to notify */ public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { mMenuItemClickListener = listener; } /** - * Set a listener that will be notified when this menu is dismissed. + * Sets a listener that will be notified when this menu is dismissed. * - * @param listener Listener to notify + * @param listener the listener to notify */ public void setOnDismissListener(OnDismissListener listener) { mDismissListener = listener; } /** - * @hide - */ - public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { - if (mMenuItemClickListener != null) { - return mMenuItemClickListener.onMenuItemClick(item); - } - return false; - } - - /** - * @hide + * Interface responsible for receiving menu item click events if the items + * themselves do not have individual item click listeners. */ - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - if (mDismissListener != null) { - mDismissListener.onDismiss(this); - } - } - - /** - * @hide - */ - public boolean onOpenSubMenu(MenuBuilder subMenu) { - // The menu presenter will handle opening the submenu itself. Nothing to do here. - return false; - } - - /** - * @hide - */ - public void onMenuModeChange(MenuBuilder menu) { + public interface OnMenuItemClickListener { + /** + * This method will be invoked when a menu item is clicked if the item + * itself did not already handle the event. + * + * @param item the menu item that was clicked + * @return {@code true} if the event was handled, {@code false} + * otherwise + */ + boolean onMenuItemClick(MenuItem item); } /** - * Interface responsible for receiving menu item click events if the items themselves - * do not have individual item click listeners. + * Callback interface used to notify the application that the menu has closed. */ - public interface OnMenuItemClickListener { + public interface OnDismissListener { /** - * This method will be invoked when a menu item is clicked if the item itself did - * not already handle the event. + * Called when the associated menu has been dismissed. * - * @param item {@link MenuItem} that was clicked - * @return <code>true</code> if the event was handled, <code>false</code> otherwise. + * @param menu the popup menu that was dismissed */ - public boolean onMenuItemClick(MenuItem item); + void onDismiss(PopupMenu menu); } } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 8e5af793e2d9..a24d37f4b097 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -100,7 +100,7 @@ public class TimePicker extends FrameLayout { * @see #getHour() */ public void setHour(int hour) { - mDelegate.setCurrentHour(hour); + mDelegate.setHour(hour); } /** @@ -110,7 +110,7 @@ public class TimePicker extends FrameLayout { * @see #setHour(int) */ public int getHour() { - return mDelegate.getCurrentHour(); + return mDelegate.getHour(); } /** @@ -120,7 +120,7 @@ public class TimePicker extends FrameLayout { * @see #getMinute() */ public void setMinute(int minute) { - mDelegate.setCurrentMinute(minute); + mDelegate.setMinute(minute); } /** @@ -130,7 +130,7 @@ public class TimePicker extends FrameLayout { * @see #setMinute(int) */ public int getMinute() { - return mDelegate.getCurrentMinute(); + return mDelegate.getMinute(); } /** @@ -150,7 +150,7 @@ public class TimePicker extends FrameLayout { @NonNull @Deprecated public Integer getCurrentHour() { - return mDelegate.getCurrentHour(); + return mDelegate.getHour(); } /** @@ -160,7 +160,7 @@ public class TimePicker extends FrameLayout { */ @Deprecated public void setCurrentMinute(@NonNull Integer currentMinute) { - mDelegate.setCurrentMinute(currentMinute); + mDelegate.setMinute(currentMinute); } /** @@ -170,7 +170,7 @@ public class TimePicker extends FrameLayout { @NonNull @Deprecated public Integer getCurrentMinute() { - return mDelegate.getCurrentMinute(); + return mDelegate.getMinute(); } /** @@ -186,7 +186,7 @@ public class TimePicker extends FrameLayout { return; } - mDelegate.setIs24HourView(is24HourView); + mDelegate.setIs24Hour(is24HourView); } /** @@ -195,7 +195,7 @@ public class TimePicker extends FrameLayout { * @see #setIs24HourView(Boolean) */ public boolean is24HourView() { - return mDelegate.is24HourView(); + return mDelegate.is24Hour(); } /** @@ -207,16 +207,6 @@ public class TimePicker extends FrameLayout { mDelegate.setOnTimeChangedListener(onTimeChangedListener); } - /** - * Sets the callback that indicates the current time is valid. - * - * @param callback the callback, may be null - * @hide - */ - public void setValidationCallback(@Nullable ValidationCallback callback) { - mDelegate.setValidationCallback(callback); - } - @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -234,12 +224,6 @@ public class TimePicker extends FrameLayout { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mDelegate.onConfigurationChanged(newConfig); - } - - @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); return mDelegate.onSaveInstanceState(superState); @@ -269,25 +253,22 @@ public class TimePicker extends FrameLayout { * for the real behavior. */ interface TimePickerDelegate { - void setCurrentHour(int currentHour); - int getCurrentHour(); + void setHour(int hour); + int getHour(); - void setCurrentMinute(int currentMinute); - int getCurrentMinute(); + void setMinute(int minute); + int getMinute(); - void setIs24HourView(boolean is24HourView); - boolean is24HourView(); + void setIs24Hour(boolean is24Hour); + boolean is24Hour(); void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener); - void setValidationCallback(ValidationCallback callback); void setEnabled(boolean enabled); boolean isEnabled(); int getBaseline(); - void onConfigurationChanged(Configuration newConfig); - Parcelable onSaveInstanceState(Parcelable superState); void onRestoreInstanceState(Parcelable state); @@ -295,16 +276,6 @@ public class TimePicker extends FrameLayout { void onPopulateAccessibilityEvent(AccessibilityEvent event); } - /** - * A callback interface for updating input validity when the TimePicker - * when included into a Dialog. - * - * @hide - */ - public static interface ValidationCallback { - void onValidationChanged(boolean valid); - } - static String[] getAmPmStrings(Context context) { final Locale locale = context.getResources().getConfiguration().locale; final LocaleData d = LocaleData.get(locale); @@ -319,43 +290,16 @@ public class TimePicker extends FrameLayout { * An abstract class which can be used as a start for TimePicker implementations */ abstract static class AbstractTimePickerDelegate implements TimePickerDelegate { - // The delegator - protected TimePicker mDelegator; - - // The context - protected Context mContext; + protected final TimePicker mDelegator; + protected final Context mContext; + protected final Locale mLocale; - // The current locale - protected Locale mCurrentLocale; - - // Callbacks protected OnTimeChangedListener mOnTimeChangedListener; - protected ValidationCallback mValidationCallback; - public AbstractTimePickerDelegate(TimePicker delegator, Context context) { + public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) { mDelegator = delegator; mContext = context; - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - } - - public void setCurrentLocale(Locale locale) { - if (locale.equals(mCurrentLocale)) { - return; - } - mCurrentLocale = locale; - } - - @Override - public void setValidationCallback(ValidationCallback callback) { - mValidationCallback = callback; - } - - protected void onValidationChanged(boolean valid) { - if (mValidationCallback != null) { - mValidationCallback.onValidationChanged(valid); - } + mLocale = context.getResources().getConfiguration().locale; } } } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 4dc5fd3e5f5c..38ce033470b6 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -19,7 +19,6 @@ package android.widget; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Parcel; @@ -89,7 +88,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private boolean mAllowAutoAdvance; private int mInitialHourOfDay; private int mInitialMinute; - private boolean mIs24HourView; + private boolean mIs24Hour; private boolean mIsAmPmAtStart; // Accessibility strings. @@ -200,19 +199,19 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl mAllowAutoAdvance = true; // Updates mHourFormat variables used below. - updateHourFormat(mCurrentLocale, mIs24HourView); + updateHourFormat(mLocale, mIs24Hour); // Update hour text field. final int minHour = mHourFormatStartsAtZero ? 0 : 1; - final int maxHour = (mIs24HourView ? 23 : 11) + minHour; + final int maxHour = (mIs24Hour ? 23 : 11) + minHour; mHourView.setRange(minHour, maxHour); mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero); // Initialize with current time. - mTempCalendar = Calendar.getInstance(mCurrentLocale); + mTempCalendar = Calendar.getInstance(mLocale); final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY); final int currentMinute = mTempCalendar.get(Calendar.MINUTE); - initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX); + initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX); } /** @@ -333,7 +332,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) { mInitialHourOfDay = hourOfDay; mInitialMinute = minute; - mIs24HourView = is24HourView; + mIs24Hour = is24HourView; updateUI(index); } @@ -352,17 +351,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } private void updateRadialPicker(int index) { - mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView); + mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24Hour); setCurrentItemShowing(index, false, true); } private void updateHeaderAmPm() { - - if (mIs24HourView) { + if (mIs24Hour) { mAmPmLayout.setVisibility(View.GONE); } else { // Ensure that AM/PM layout is in the correct position. - final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm"); + final String dateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm"); final boolean isAmPmAtStart = dateTimePattern.startsWith("a"); setAmPmAtStart(isAmPmAtStart); @@ -395,35 +393,32 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * Set the current hour. */ @Override - public void setCurrentHour(int currentHour) { - if (mInitialHourOfDay == currentHour) { - return; + public void setHour(int hour) { + if (mInitialHourOfDay != hour) { + mInitialHourOfDay = hour; + updateHeaderHour(hour, true); + updateHeaderAmPm(); + mRadialTimePickerView.setCurrentHour(hour); + mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM); + mDelegator.invalidate(); + onTimeChanged(); } - mInitialHourOfDay = currentHour; - updateHeaderHour(currentHour, true); - updateHeaderAmPm(); - mRadialTimePickerView.setCurrentHour(currentHour); - mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM); - mDelegator.invalidate(); - onTimeChanged(); } /** - * @return The current hour in the range (0-23). + * @return the current hour in the range (0-23) */ @Override - public int getCurrentHour() { - int currentHour = mRadialTimePickerView.getCurrentHour(); - if (mIs24HourView) { + public int getHour() { + final int currentHour = mRadialTimePickerView.getCurrentHour(); + if (mIs24Hour) { return currentHour; + } + + if (mRadialTimePickerView.getAmOrPm() == PM) { + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; } else { - switch(mRadialTimePickerView.getAmOrPm()) { - case PM: - return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; - case AM: - default: - return currentHour % HOURS_IN_HALF_DAY; - } + return currentHour % HOURS_IN_HALF_DAY; } } @@ -431,48 +426,48 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * Set the current minute (0-59). */ @Override - public void setCurrentMinute(int currentMinute) { - if (mInitialMinute == currentMinute) { - return; + public void setMinute(int minute) { + if (mInitialMinute != minute) { + mInitialMinute = minute; + updateHeaderMinute(minute, true); + mRadialTimePickerView.setCurrentMinute(minute); + mDelegator.invalidate(); + onTimeChanged(); } - mInitialMinute = currentMinute; - updateHeaderMinute(currentMinute, true); - mRadialTimePickerView.setCurrentMinute(currentMinute); - mDelegator.invalidate(); - onTimeChanged(); } /** * @return The current minute. */ @Override - public int getCurrentMinute() { + public int getMinute() { return mRadialTimePickerView.getCurrentMinute(); } /** - * Set whether in 24 hour or AM/PM mode. + * Sets whether time is displayed in 24-hour mode or 12-hour mode with + * AM/PM indicators. * - * @param is24HourView True = 24 hour mode. False = AM/PM. + * @param is24Hour {@code true} to display time in 24-hour mode or + * {@code false} for 12-hour mode with AM/PM */ - @Override - public void setIs24HourView(boolean is24HourView) { - if (is24HourView == mIs24HourView) { - return; - } - - mIs24HourView = is24HourView; - mInitialHourOfDay = getCurrentHour(); + public void setIs24Hour(boolean is24Hour) { + if (mIs24Hour != is24Hour) { + mIs24Hour = is24Hour; + mInitialHourOfDay = getHour(); - updateUI(mRadialTimePickerView.getCurrentItemShowing()); + updateUI(mRadialTimePickerView.getCurrentItemShowing()); + } } /** - * @return true if this is in 24 hour view else false. + * @return {@code true} if time is displayed in 24-hour mode, or + * {@code false} if time is displayed in 12-hour mode with AM/PM + * indicators */ @Override - public boolean is24HourView() { - return mIs24HourView; + public boolean is24Hour() { + return mIs24Hour; } @Override @@ -502,14 +497,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } @Override - public void onConfigurationChanged(Configuration newConfig) { - updateUI(mRadialTimePickerView.getCurrentItemShowing()); - } - - @Override public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getCurrentHour(), getCurrentMinute(), - is24HourView(), getCurrentItemShowing()); + return new SavedState(superState, getHour(), getMinute(), + is24Hour(), getCurrentItemShowing()); } @Override @@ -520,12 +510,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } @Override - public void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - mTempCalendar = Calendar.getInstance(locale); - } - - @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { onPopulateAccessibilityEvent(event); return true; @@ -534,13 +518,13 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { int flags = DateUtils.FORMAT_SHOW_TIME; - if (mIs24HourView) { + if (mIs24Hour) { flags |= DateUtils.FORMAT_24HOUR; } else { flags |= DateUtils.FORMAT_12HOUR; } - mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); - mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); + mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour()); + mTempCalendar.set(Calendar.MINUTE, getMinute()); String selectedDate = DateUtils.formatDateTime(mContext, mTempCalendar.getTimeInMillis(), flags); event.getText().add(selectedDate); @@ -559,8 +543,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private void onTimeChanged() { mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, - getCurrentHour(), getCurrentMinute()); + mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute()); } } @@ -666,7 +649,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute()); + mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute()); } } @@ -677,14 +660,14 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * @return a localized hour number */ private int getLocalizedHour(int hourOfDay) { - if (!mIs24HourView) { + if (!mIs24Hour) { // Convert to hour-of-am-pm. hourOfDay %= 12; } if (!mHourFormatStartsAtZero && hourOfDay == 0) { // Convert to clock-hour (either of-day or of-am-pm). - hourOfDay = mIs24HourView ? 24 : 12; + hourOfDay = mIs24Hour ? 24 : 12; } return hourOfDay; @@ -716,8 +699,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl * separator as the character which is just after the hour marker in the returned pattern. */ private void updateHeaderSeparator() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, - (mIs24HourView) ? "Hm" : "hm"); + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, + (mIs24Hour) ? "Hm" : "hm"); final String separatorText; // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats final char[] hourFormats = {'H', 'h', 'K', 'k'}; @@ -819,14 +802,14 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl private final Runnable mCommitHour = new Runnable() { @Override public void run() { - setCurrentHour(mHourView.getValue()); + setHour(mHourView.getValue()); } }; private final Runnable mCommitMinute = new Runnable() { @Override public void run() { - setCurrentMinute(mMinuteView.getValue()); + setMinute(mMinuteView.getValue()); } }; diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java index 8741cc3b784f..2ed230bf1b35 100644 --- a/core/java/android/widget/TimePickerSpinnerDelegate.java +++ b/core/java/android/widget/TimePickerSpinnerDelegate.java @@ -17,7 +17,6 @@ package android.widget; import android.content.Context; -import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; @@ -33,7 +32,6 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.R; import java.util.Calendar; -import java.util.Locale; import libcore.icu.LocaleData; @@ -92,7 +90,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { updateInputState(); - if (!is24HourView()) { + if (!is24Hour()) { if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { mIsAm = !mIsAm; @@ -124,14 +122,14 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { int maxValue = mMinuteSpinner.getMaxValue(); if (oldVal == maxValue && newVal == minValue) { int newHour = mHourSpinner.getValue() + 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { + if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) { mIsAm = !mIsAm; updateAmPmControl(); } mHourSpinner.setValue(newHour); } else if (oldVal == minValue && newVal == maxValue) { int newHour = mHourSpinner.getValue() - 1; - if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { + if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } @@ -204,8 +202,8 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { updateAmPmControl(); // set to current time - setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); - setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); + setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); + setMinute(mTempCalendar.get(Calendar.MINUTE)); if (!isEnabled()) { setEnabled(false); @@ -221,7 +219,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } private void getHourFormatData() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, (mIs24HourView) ? "Hm" : "hm"); final int lengthPattern = bestDateTimePattern.length(); mHourWithTwoDigit = false; @@ -241,7 +239,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } private boolean isAmPmAtStart() { - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm" /* skeleton */); return bestDateTimePattern.startsWith("a"); @@ -257,7 +255,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { */ private void setDividerText() { final String skeleton = (mIs24HourView) ? "Hm" : "hm"; - final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, skeleton); final String separatorText; int hourIndex = bestDateTimePattern.lastIndexOf('H'); @@ -279,16 +277,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public void setCurrentHour(int currentHour) { - setCurrentHour(currentHour, true); + public void setHour(int hour) { + setCurrentHour(hour, true); } private void setCurrentHour(int currentHour, boolean notifyTimeChanged) { // why was Integer used in the first place? - if (currentHour == getCurrentHour()) { + if (currentHour == getHour()) { return; } - if (!is24HourView()) { + if (!is24Hour()) { // convert [0,23] ordinal to wall clock display if (currentHour >= HOURS_IN_HALF_DAY) { mIsAm = false; @@ -310,9 +308,9 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public int getCurrentHour() { + public int getHour() { int currentHour = mHourSpinner.getValue(); - if (is24HourView()) { + if (is24Hour()) { return currentHour; } else if (mIsAm) { return currentHour % HOURS_IN_HALF_DAY; @@ -322,28 +320,27 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public void setCurrentMinute(int currentMinute) { - if (currentMinute == getCurrentMinute()) { + public void setMinute(int minute) { + if (minute == getMinute()) { return; } - mMinuteSpinner.setValue(currentMinute); + mMinuteSpinner.setValue(minute); onTimeChanged(); } @Override - public int getCurrentMinute() { + public int getMinute() { return mMinuteSpinner.getValue(); } - @Override - public void setIs24HourView(boolean is24HourView) { - if (mIs24HourView == is24HourView) { + public void setIs24Hour(boolean is24Hour) { + if (mIs24HourView == is24Hour) { return; } // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! - int currentHour = getCurrentHour(); + int currentHour = getHour(); // Order is important here. - mIs24HourView = is24HourView; + mIs24HourView = is24Hour; getHourFormatData(); updateHourControl(); // set value after spinner range is updated @@ -353,7 +350,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public boolean is24HourView() { + public boolean is24Hour() { return mIs24HourView; } @@ -388,20 +385,15 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } @Override - public void onConfigurationChanged(Configuration newConfig) { - setCurrentLocale(newConfig.locale); - } - - @Override public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getCurrentHour(), getCurrentMinute()); + return new SavedState(superState, getHour(), getMinute()); } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; - setCurrentHour(ss.getHour()); - setCurrentMinute(ss.getMinute()); + setHour(ss.getHour()); + setMinute(ss.getMinute()); } @Override @@ -418,8 +410,8 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } else { flags |= DateUtils.FORMAT_12HOUR; } - mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); - mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); + mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour()); + mTempCalendar.set(Calendar.MINUTE, getMinute()); String selectedDateUtterance = DateUtils.formatDateTime(mContext, mTempCalendar.getTimeInMillis(), flags); event.getText().add(selectedDateUtterance); @@ -447,7 +439,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } private void updateAmPmControl() { - if (is24HourView()) { + if (is24Hour()) { if (mAmPmSpinner != null) { mAmPmSpinner.setVisibility(View.GONE); } else { @@ -466,27 +458,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } - /** - * Sets the current locale. - * - * @param locale The current locale. - */ - @Override - public void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - mTempCalendar = Calendar.getInstance(locale); - } - private void onTimeChanged() { mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); if (mOnTimeChangedListener != null) { - mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), - getCurrentMinute()); + mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), + getMinute()); } } private void updateHourControl() { - if (is24HourView()) { + if (is24Hour()) { // 'k' means 1-24 hour if (mHourFormat == 'k') { mHourSpinner.setMinValue(1); @@ -509,7 +490,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } private void updateMinuteControl() { - if (is24HourView()) { + if (is24Hour()) { mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); } else { mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java index e26b27d5820c..c067da7e1dec 100644 --- a/core/java/com/android/internal/os/BaseCommand.java +++ b/core/java/com/android/internal/os/BaseCommand.java @@ -17,13 +17,19 @@ package com.android.internal.os; +import android.os.ShellCommand; + import java.io.PrintStream; public abstract class BaseCommand { - protected String[] mArgs; - private int mNextArg; - private String mCurArgData; + final protected ShellCommand mArgs = new ShellCommand() { + @Override public int onCommand(String cmd) { + return 0; + } + @Override public void onHelp() { + } + }; // These are magic strings understood by the Eclipse plugin. public static final String FATAL_ERROR_CODE = "Error type 1"; @@ -39,9 +45,7 @@ public abstract class BaseCommand { return; } - mArgs = args; - mNextArg = 0; - mCurArgData = null; + mArgs.init(null, null, null, null, args, 0); try { onRun(); @@ -87,32 +91,7 @@ public abstract class BaseCommand { * starts with '-'. If the next argument is not an option, null is returned. */ public String nextOption() { - if (mCurArgData != null) { - String prev = mArgs[mNextArg - 1]; - throw new IllegalArgumentException("No argument expected after \"" + prev + "\""); - } - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - if (!arg.startsWith("-")) { - return null; - } - mNextArg++; - if (arg.equals("--")) { - return null; - } - if (arg.length() > 1 && arg.charAt(1) != '-') { - if (arg.length() > 2) { - mCurArgData = arg.substring(2); - return arg.substring(0, 2); - } else { - mCurArgData = null; - return arg; - } - } - mCurArgData = null; - return arg; + return mArgs.getNextOption(); } /** @@ -120,15 +99,7 @@ public abstract class BaseCommand { * no arguments left, return null. */ public String nextArg() { - if (mCurArgData != null) { - String arg = mCurArgData; - mCurArgData = null; - return arg; - } else if (mNextArg < mArgs.length) { - return mArgs[mNextArg++]; - } else { - return null; - } + return mArgs.getNextArg(); } /** @@ -136,11 +107,6 @@ public abstract class BaseCommand { * no arguments left, throws an IllegalArgumentException to report this to the user. */ public String nextArgRequired() { - String arg = nextArg(); - if (arg == null) { - String prev = mArgs[mNextArg - 1]; - throw new IllegalArgumentException("Argument expected after \"" + prev + "\""); - } - return arg; + return mArgs.getNextArgRequired(); } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 197004c93dcb..8186378bfb6f 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -39,10 +39,8 @@ public final class Zygote { public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3; /** Enable logging of third-party JNI activity. */ public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; - /** enable the JIT compiler */ - public static final int DEBUG_ENABLE_JIT = 1 << 5; /** Force generation of native debugging information. */ - public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 6; + public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = 0; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 3e86fac74f2d..a40f9a82ace7 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -322,7 +322,7 @@ class ZygoteConnection { /** * From --enable-debugger, --enable-checkjni, --enable-assert, - * --enable-safemode, --enable-jit, --generate-debug-info and --enable-jni-logging. + * --enable-safemode, --generate-debug-info and --enable-jni-logging. */ int debugFlags; @@ -432,8 +432,6 @@ class ZygoteConnection { debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; } else if (arg.equals("--enable-checkjni")) { debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; - } else if (arg.equals("--enable-jit")) { - debugFlags |= Zygote.DEBUG_ENABLE_JIT; } else if (arg.equals("--generate-debug-info")) { debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; } else if (arg.equals("--enable-jni-logging")) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java new file mode 100644 index 000000000000..4f38ff34e7da --- /dev/null +++ b/core/java/com/android/internal/policy/DecorView.java @@ -0,0 +1,1632 @@ +/* + * 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.internal.policy; + +import com.android.internal.R; +import com.android.internal.view.FloatingActionMode; +import com.android.internal.view.RootViewSurfaceTaker; +import com.android.internal.view.StandaloneActionMode; +import com.android.internal.view.menu.ContextMenuBuilder; +import com.android.internal.view.menu.MenuDialogHelper; +import com.android.internal.view.menu.MenuPopupHelper; +import com.android.internal.widget.ActionBarContextView; +import com.android.internal.widget.BackgroundFallback; +import com.android.internal.widget.FloatingToolbar; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.ActionMode; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.InputQueue; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; +import android.widget.PopupWindow; + +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.getMode; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; +import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; + +class DecorView extends FrameLayout implements RootViewSurfaceTaker { + private static final String TAG = "DecorView"; + + private static final boolean SWEEP_OPEN_MENU = false; + + int mDefaultOpacity = PixelFormat.OPAQUE; + + /** The feature ID of the panel, or -1 if this is the application's DecorView */ + private final int mFeatureId; + + private final Rect mDrawingBounds = new Rect(); + + private final Rect mBackgroundPadding = new Rect(); + + private final Rect mFramePadding = new Rect(); + + private final Rect mFrameOffsets = new Rect(); + + // True if a non client area decor exists. + private boolean mHasNonClientDecor = false; + + private boolean mChanging; + + private Drawable mMenuBackground; + private boolean mWatchingForMenu; + private int mDownY; + + ActionMode mPrimaryActionMode; + private ActionMode mFloatingActionMode; + private ActionBarContextView mPrimaryActionModeView; + private PopupWindow mPrimaryActionModePopup; + private Runnable mShowPrimaryActionModePopup; + private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private View mFloatingActionModeOriginatingView; + private FloatingToolbar mFloatingToolbar; + private ObjectAnimator mFadeAnim; + + // View added at runtime to draw under the status bar area + private View mStatusGuard; + // View added at runtime to draw under the navigation bar area + private View mNavigationGuard; + + private final ColorViewState mStatusColorViewState = new ColorViewState( + SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, + Gravity.TOP, Gravity.LEFT, + Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.statusBarBackground, + FLAG_FULLSCREEN); + private final ColorViewState mNavigationColorViewState = new ColorViewState( + SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, + Gravity.BOTTOM, Gravity.RIGHT, + Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.navigationBarBackground, + 0 /* hideWindowFlag */); + + private final Interpolator mShowInterpolator; + private final Interpolator mHideInterpolator; + private final int mBarEnterExitDuration; + + private final BackgroundFallback mBackgroundFallback = new BackgroundFallback(); + + private int mLastTopInset = 0; + private int mLastBottomInset = 0; + private int mLastRightInset = 0; + private boolean mLastHasTopStableInset = false; + private boolean mLastHasBottomStableInset = false; + private boolean mLastHasRightStableInset = false; + private int mLastWindowFlags = 0; + + private int mRootScrollY = 0; + + private PhoneWindow mWindow; + + ViewGroup mContentRoot; + + private Rect mTempRect; + private Rect mOutsets = new Rect(); + + DecorView(Context context, int featureId, PhoneWindow window) { + super(context); + mFeatureId = featureId; + + mShowInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.linear_out_slow_in); + mHideInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.fast_out_linear_in); + + mBarEnterExitDuration = context.getResources().getInteger( + R.integer.dock_enter_exit_duration); + + setWindow(window); + } + + public void setBackgroundFallback(int resId) { + mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null); + setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback()); + } + + @Override + public void onDraw(Canvas c) { + super.onDraw(c); + mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + final int keyCode = event.getKeyCode(); + final int action = event.getAction(); + final boolean isDown = action == KeyEvent.ACTION_DOWN; + + if (isDown && (event.getRepeatCount() == 0)) { + // First handle chording of panel key: if a panel key is held + // but not released, try to execute a shortcut in it. + if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { + boolean handled = dispatchKeyShortcutEvent(event); + if (handled) { + return true; + } + } + + // If a panel is open, perform a shortcut on it without the + // chorded panel key + if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { + if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { + return true; + } + } + } + + if (!mWindow.isDestroyed()) { + final Window.Callback cb = mWindow.getCallback(); + final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) + : super.dispatchKeyEvent(event); + if (handled) { + return true; + } + } + + return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) + : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent ev) { + // If the panel is already prepared, then perform the shortcut using it. + boolean handled; + if (mWindow.mPreparedPanel != null) { + handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev, + Menu.FLAG_PERFORM_NO_CLOSE); + if (handled) { + if (mWindow.mPreparedPanel != null) { + mWindow.mPreparedPanel.isHandled = true; + } + return true; + } + } + + // Shortcut not handled by the panel. Dispatch to the view hierarchy. + final Window.Callback cb = mWindow.getCallback(); + handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev); + if (handled) { + return true; + } + + // If the panel is not prepared, then we may be trying to handle a shortcut key + // combination such as Control+C. Temporarily prepare the panel then mark it + // unprepared again when finished to ensure that the panel will again be prepared + // the next time it is shown for real. + PhoneWindow.PanelFeatureState st = + mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false); + if (st != null && mWindow.mPreparedPanel == null) { + mWindow.preparePanel(st, ev); + handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev, + Menu.FLAG_PERFORM_NO_CLOSE); + st.isPrepared = false; + if (handled) { + return true; + } + } + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final Window.Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) { + final Window.Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + final Window.Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev); + } + + public boolean superDispatchKeyEvent(KeyEvent event) { + // Give priority to closing action modes if applicable. + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + final int action = event.getAction(); + // Back cancels action modes first. + if (mPrimaryActionMode != null) { + if (action == KeyEvent.ACTION_UP) { + mPrimaryActionMode.finish(); + } + return true; + } + } + + return super.dispatchKeyEvent(event); + } + + public boolean superDispatchKeyShortcutEvent(KeyEvent event) { + return super.dispatchKeyShortcutEvent(event); + } + + public boolean superDispatchTouchEvent(MotionEvent event) { + return super.dispatchTouchEvent(event); + } + + public boolean superDispatchTrackballEvent(MotionEvent event) { + return super.dispatchTrackballEvent(event); + } + + public boolean superDispatchGenericMotionEvent(MotionEvent event) { + return super.dispatchGenericMotionEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return onInterceptTouchEvent(event); + } + + private boolean isOutOfInnerBounds(int x, int y) { + return x < 0 || y < 0 || x > getWidth() || y > getHeight(); + } + + private boolean isOutOfBounds(int x, int y) { + return x < -5 || y < -5 || x > (getWidth() + 5) + || y > (getHeight() + 5); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + int action = event.getAction(); + if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) { + // Don't dispatch ACTION_DOWN to the non client decor if the window is + // resizable and the event was (starting) outside the window. + // Window resizing events should be handled by WindowManager. + // TODO: Investigate how to handle the outside touch in window manager + // without generating these events. + // Currently we receive these because we need to enlarge the window's + // touch region so that the monitor channel receives the events + // in the outside touch area. + if (action == MotionEvent.ACTION_DOWN) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + if (isOutOfInnerBounds(x, y)) { + return true; + } + } + } + + if (mFeatureId >= 0) { + if (action == MotionEvent.ACTION_DOWN) { + int x = (int)event.getX(); + int y = (int)event.getY(); + if (isOutOfBounds(x, y)) { + mWindow.closePanel(mFeatureId); + return true; + } + } + } + + if (!SWEEP_OPEN_MENU) { + return false; + } + + if (mFeatureId >= 0) { + if (action == MotionEvent.ACTION_DOWN) { + Log.i(TAG, "Watchiing!"); + mWatchingForMenu = true; + mDownY = (int) event.getY(); + return false; + } + + if (!mWatchingForMenu) { + return false; + } + + int y = (int)event.getY(); + if (action == MotionEvent.ACTION_MOVE) { + if (y > (mDownY+30)) { + Log.i(TAG, "Closing!"); + mWindow.closePanel(mFeatureId); + mWatchingForMenu = false; + return true; + } + } else if (action == MotionEvent.ACTION_UP) { + mWatchingForMenu = false; + } + + return false; + } + + //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() + // + " (in " + getHeight() + ")"); + + if (action == MotionEvent.ACTION_DOWN) { + int y = (int)event.getY(); + if (y >= (getHeight()-5) && !mWindow.hasChildren()) { + Log.i(TAG, "Watching!"); + mWatchingForMenu = true; + } + return false; + } + + if (!mWatchingForMenu) { + return false; + } + + int y = (int)event.getY(); + if (action == MotionEvent.ACTION_MOVE) { + if (y < (getHeight()-30)) { + Log.i(TAG, "Opening!"); + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent( + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); + mWatchingForMenu = false; + return true; + } + } else if (action == MotionEvent.ACTION_UP) { + mWatchingForMenu = false; + } + + return false; + } + + @Override + public void sendAccessibilityEvent(int eventType) { + if (!AccessibilityManager.getInstance(mContext).isEnabled()) { + return; + } + + // if we are showing a feature that should be announced and one child + // make this child the event source since this is the feature itself + // otherwise the callback will take over and announce its client + if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL || + mFeatureId == Window.FEATURE_CONTEXT_MENU || + mFeatureId == Window.FEATURE_PROGRESS || + mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS) + && getChildCount() == 1) { + getChildAt(0).sendAccessibilityEvent(eventType); + } else { + super.sendAccessibilityEvent(eventType); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { + final Window.Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed()) { + if (cb.dispatchPopulateAccessibilityEvent(event)) { + return true; + } + } + return super.dispatchPopulateAccessibilityEventInternal(event); + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + boolean changed = super.setFrame(l, t, r, b); + if (changed) { + final Rect drawingBounds = mDrawingBounds; + getDrawingRect(drawingBounds); + + Drawable fg = getForeground(); + if (fg != null) { + final Rect frameOffsets = mFrameOffsets; + drawingBounds.left += frameOffsets.left; + drawingBounds.top += frameOffsets.top; + drawingBounds.right -= frameOffsets.right; + drawingBounds.bottom -= frameOffsets.bottom; + fg.setBounds(drawingBounds); + final Rect framePadding = mFramePadding; + drawingBounds.left += framePadding.left - frameOffsets.left; + drawingBounds.top += framePadding.top - frameOffsets.top; + drawingBounds.right -= framePadding.right - frameOffsets.right; + drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; + } + + Drawable bg = getBackground(); + if (bg != null) { + bg.setBounds(drawingBounds); + } + + if (SWEEP_OPEN_MENU) { + if (mMenuBackground == null && mFeatureId < 0 + && mWindow.getAttributes().height + == WindowManager.LayoutParams.MATCH_PARENT) { + mMenuBackground = getContext().getDrawable( + R.drawable.menu_background); + } + if (mMenuBackground != null) { + mMenuBackground.setBounds(drawingBounds.left, + drawingBounds.bottom-6, drawingBounds.right, + drawingBounds.bottom+20); + } + } + } + return changed; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; + + final int widthMode = getMode(widthMeasureSpec); + final int heightMode = getMode(heightMeasureSpec); + + boolean fixedWidth = false; + if (widthMode == AT_MOST) { + final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor + : mWindow.mFixedWidthMajor; + if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { + final int w; + if (tvw.type == TypedValue.TYPE_DIMENSION) { + w = (int) tvw.getDimension(metrics); + } else if (tvw.type == TypedValue.TYPE_FRACTION) { + w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); + } else { + w = 0; + } + + if (w > 0) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + widthMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.min(w, widthSize), EXACTLY); + fixedWidth = true; + } + } + } + + if (heightMode == AT_MOST) { + final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor + : mWindow.mFixedHeightMinor; + if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { + final int h; + if (tvh.type == TypedValue.TYPE_DIMENSION) { + h = (int) tvh.getDimension(metrics); + } else if (tvh.type == TypedValue.TYPE_FRACTION) { + h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); + } else { + h = 0; + } + if (h > 0) { + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.min(h, heightSize), EXACTLY); + } + } + } + + getOutsets(mOutsets); + if (mOutsets.top > 0 || mOutsets.bottom > 0) { + int mode = MeasureSpec.getMode(heightMeasureSpec); + if (mode != MeasureSpec.UNSPECIFIED) { + int height = MeasureSpec.getSize(heightMeasureSpec); + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + height + mOutsets.top + mOutsets.bottom, mode); + } + } + if (mOutsets.left > 0 || mOutsets.right > 0) { + int mode = MeasureSpec.getMode(widthMeasureSpec); + if (mode != MeasureSpec.UNSPECIFIED) { + int width = MeasureSpec.getSize(widthMeasureSpec); + widthMeasureSpec = MeasureSpec.makeMeasureSpec( + width + mOutsets.left + mOutsets.right, mode); + } + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + boolean measure = false; + + widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); + + if (!fixedWidth && widthMode == AT_MOST) { + final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; + if (tv.type != TypedValue.TYPE_NULL) { + final int min; + if (tv.type == TypedValue.TYPE_DIMENSION) { + min = (int)tv.getDimension(metrics); + } else if (tv.type == TypedValue.TYPE_FRACTION) { + min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); + } else { + min = 0; + } + + if (width < min) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); + measure = true; + } + } + } + + // TODO: Support height? + + if (measure) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + getOutsets(mOutsets); + if (mOutsets.left > 0) { + offsetLeftAndRight(-mOutsets.left); + } + if (mOutsets.top > 0) { + offsetTopAndBottom(-mOutsets.top); + } + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + if (mMenuBackground != null) { + mMenuBackground.draw(canvas); + } + } + + @Override + public boolean showContextMenuForChild(View originalView) { + // Reuse the context menu builder + if (mWindow.mContextMenu == null) { + mWindow.mContextMenu = new ContextMenuBuilder(getContext()); + mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback); + } else { + mWindow.mContextMenu.clearAll(); + } + + final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView, + originalView.getWindowToken()); + if (helper != null) { + helper.setPresenterCallback(mWindow.mContextMenuCallback); + } else if (mWindow.mContextMenuHelper != null) { + // No menu to show, but if we have a menu currently showing it just became blank. + // Close it. + mWindow.mContextMenuHelper.dismiss(); + } + mWindow.mContextMenuHelper = helper; + return helper != null; + } + + @Override + public boolean showContextMenuForChild(View originalView, float x, float y) { + // Reuse the context menu builder + if (mWindow.mContextMenu == null) { + mWindow.mContextMenu = new ContextMenuBuilder(getContext()); + mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback); + } else { + mWindow.mContextMenu.clearAll(); + } + + final MenuPopupHelper helper = mWindow.mContextMenu.showPopup( + getContext(), originalView, x, y); + if (helper != null) { + helper.setCallback(mWindow.mContextMenuCallback); + } else if (mWindow.mContextMenuPopupHelper != null) { + // No menu to show, but if we have a menu currently showing it just became blank. + // Close it. + mWindow.mContextMenuPopupHelper.dismiss(); + } + mWindow.mContextMenuPopupHelper = helper; + return helper != null; + } + + @Override + public ActionMode startActionModeForChild(View originalView, + ActionMode.Callback callback) { + return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY); + } + + @Override + public ActionMode startActionModeForChild( + View child, ActionMode.Callback callback, int type) { + return startActionMode(child, callback, type); + } + + @Override + public ActionMode startActionMode(ActionMode.Callback callback) { + return startActionMode(callback, ActionMode.TYPE_PRIMARY); + } + + @Override + public ActionMode startActionMode(ActionMode.Callback callback, int type) { + return startActionMode(this, callback, type); + } + + private ActionMode startActionMode( + View originatingView, ActionMode.Callback callback, int type) { + ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); + ActionMode mode = null; + if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { + try { + mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type); + } catch (AbstractMethodError ame) { + // Older apps might not implement the typed version of this method. + if (type == ActionMode.TYPE_PRIMARY) { + try { + mode = mWindow.getCallback().onWindowStartingActionMode( + wrappedCallback); + } catch (AbstractMethodError ame2) { + // Older apps might not implement this callback method at all. + } + } + } + } + if (mode != null) { + if (mode.getType() == ActionMode.TYPE_PRIMARY) { + cleanupPrimaryActionMode(); + mPrimaryActionMode = mode; + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } + mFloatingActionMode = mode; + } + } else { + mode = createActionMode(type, wrappedCallback, originatingView); + if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { + setHandledActionMode(mode); + } else { + mode = null; + } + } + if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) { + try { + mWindow.getCallback().onActionModeStarted(mode); + } catch (AbstractMethodError ame) { + // Older apps might not implement this callback method. + } + } + return mode; + } + + private void cleanupPrimaryActionMode() { + if (mPrimaryActionMode != null) { + mPrimaryActionMode.finish(); + mPrimaryActionMode = null; + } + if (mPrimaryActionModeView != null) { + mPrimaryActionModeView.killMode(); + } + } + + private void cleanupFloatingActionModeViews() { + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } + if (mFloatingActionModeOriginatingView != null) { + if (mFloatingToolbarPreDrawListener != null) { + mFloatingActionModeOriginatingView.getViewTreeObserver() + .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); + mFloatingToolbarPreDrawListener = null; + } + mFloatingActionModeOriginatingView = null; + } + } + + public void startChanging() { + mChanging = true; + } + + public void finishChanging() { + mChanging = false; + drawableChanged(); + } + + public void setWindowBackground(Drawable drawable) { + if (getBackground() != drawable) { + setBackgroundDrawable(drawable); + if (drawable != null) { + drawable.getPadding(mBackgroundPadding); + } else { + mBackgroundPadding.setEmpty(); + } + drawableChanged(); + } + } + + public void setWindowFrame(Drawable drawable) { + if (getForeground() != drawable) { + setForeground(drawable); + if (drawable != null) { + drawable.getPadding(mFramePadding); + } else { + mFramePadding.setEmpty(); + } + drawableChanged(); + } + } + + @Override + public void onWindowSystemUiVisibilityChanged(int visible) { + updateColorViews(null /* insets */, true /* animate */); + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mFrameOffsets.set(insets.getSystemWindowInsets()); + insets = updateColorViews(insets, true /* animate */); + insets = updateStatusGuard(insets); + updateNavigationGuard(insets); + if (getForeground() != null) { + drawableChanged(); + } + return insets; + } + + @Override + public boolean isTransitionGroup() { + return false; + } + + WindowInsets updateColorViews(WindowInsets insets, boolean animate) { + WindowManager.LayoutParams attrs = mWindow.getAttributes(); + int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); + + if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) { + boolean disallowAnimate = !isLaidOut(); + disallowAnimate |= ((mLastWindowFlags ^ attrs.flags) + & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; + mLastWindowFlags = attrs.flags; + + if (insets != null) { + mLastTopInset = Math.min(insets.getStableInsetTop(), + insets.getSystemWindowInsetTop()); + mLastBottomInset = Math.min(insets.getStableInsetBottom(), + insets.getSystemWindowInsetBottom()); + mLastRightInset = Math.min(insets.getStableInsetRight(), + insets.getSystemWindowInsetRight()); + + // Don't animate if the presence of stable insets has changed, because that + // indicates that the window was either just added and received them for the + // first time, or the window size or position has changed. + boolean hasTopStableInset = insets.getStableInsetTop() != 0; + disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); + mLastHasTopStableInset = hasTopStableInset; + + boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; + disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); + mLastHasBottomStableInset = hasBottomStableInset; + + boolean hasRightStableInset = insets.getStableInsetRight() != 0; + disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); + mLastHasRightStableInset = hasRightStableInset; + } + + boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0; + int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset; + updateColorViewInt(mNavigationColorViewState, sysUiVisibility, + mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge, + 0 /* rightInset */, animate && !disallowAnimate); + + boolean statusBarNeedsRightInset = navBarToRightEdge + && mNavigationColorViewState.present; + int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0; + updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor, + mLastTopInset, false /* matchVertical */, statusBarRightInset, + animate && !disallowAnimate); + } + + // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need + // to ensure that the rest of the view hierarchy doesn't notice it, unless they've + // explicitly asked for it. + + boolean consumingNavBar = + (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 + && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 + && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + + int consumedRight = consumingNavBar ? mLastRightInset : 0; + int consumedBottom = consumingNavBar ? mLastBottomInset : 0; + + if (mContentRoot != null + && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { + MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); + if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) { + lp.rightMargin = consumedRight; + lp.bottomMargin = consumedBottom; + mContentRoot.setLayoutParams(lp); + + if (insets == null) { + // The insets have changed, but we're not currently in the process + // of dispatching them. + requestApplyInsets(); + } + } + if (insets != null) { + insets = insets.replaceSystemWindowInsets( + insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight() - consumedRight, + insets.getSystemWindowInsetBottom() - consumedBottom); + } + } + + if (insets != null) { + insets = insets.consumeStableInsets(); + } + return insets; + } + + /** + * Update a color view + * + * @param state the color view to update. + * @param sysUiVis the current systemUiVisibility to apply. + * @param color the current color to apply. + * @param size the current size in the non-parent-matching dimension. + * @param verticalBar if true the view is attached to a vertical edge, otherwise to a + * horizontal edge, + * @param rightMargin rightMargin for the color view. + * @param animate if true, the change will be animated. + */ + private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, + int size, boolean verticalBar, int rightMargin, boolean animate) { + state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0 + && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0 + && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; + boolean show = state.present + && (color & Color.BLACK) != 0 + && (mWindow.getAttributes().flags & state.translucentFlag) == 0; + + boolean visibilityChanged = false; + View view = state.view; + + int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; + int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; + int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity; + + if (view == null) { + if (show) { + state.view = view = new View(mContext); + view.setBackgroundColor(color); + view.setTransitionName(state.transitionName); + view.setId(state.id); + visibilityChanged = true; + view.setVisibility(INVISIBLE); + state.targetVisibility = VISIBLE; + + LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, + resolvedGravity); + lp.rightMargin = rightMargin; + addView(view, lp); + updateColorViewTranslations(); + } + } else { + int vis = show ? VISIBLE : INVISIBLE; + visibilityChanged = state.targetVisibility != vis; + state.targetVisibility = vis; + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + if (lp.height != resolvedHeight || lp.width != resolvedWidth + || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) { + lp.height = resolvedHeight; + lp.width = resolvedWidth; + lp.gravity = resolvedGravity; + lp.rightMargin = rightMargin; + view.setLayoutParams(lp); + } + if (show) { + view.setBackgroundColor(color); + } + } + if (visibilityChanged) { + view.animate().cancel(); + if (animate) { + if (show) { + if (view.getVisibility() != VISIBLE) { + view.setVisibility(VISIBLE); + view.setAlpha(0.0f); + } + view.animate().alpha(1.0f).setInterpolator(mShowInterpolator). + setDuration(mBarEnterExitDuration); + } else { + view.animate().alpha(0.0f).setInterpolator(mHideInterpolator) + .setDuration(mBarEnterExitDuration) + .withEndAction(new Runnable() { + @Override + public void run() { + state.view.setAlpha(1.0f); + state.view.setVisibility(INVISIBLE); + } + }); + } + } else { + view.setAlpha(1.0f); + view.setVisibility(show ? VISIBLE : INVISIBLE); + } + } + } + + private void updateColorViewTranslations() { + // Put the color views back in place when they get moved off the screen + // due to the the ViewRootImpl panning. + int rootScrollY = mRootScrollY; + if (mStatusColorViewState.view != null) { + mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0); + } + if (mNavigationColorViewState.view != null) { + mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0); + } + } + + private WindowInsets updateStatusGuard(WindowInsets insets) { + boolean showStatusGuard = false; + // Show the status guard when the non-overlay contextual action bar is showing + if (mPrimaryActionModeView != null) { + if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) { + // Insets are magic! + final MarginLayoutParams mlp = (MarginLayoutParams) + mPrimaryActionModeView.getLayoutParams(); + boolean mlpChanged = false; + if (mPrimaryActionModeView.isShown()) { + if (mTempRect == null) { + mTempRect = new Rect(); + } + final Rect rect = mTempRect; + + // If the parent doesn't consume the insets, manually + // apply the default system window insets. + mWindow.mContentParent.computeSystemWindowInsets(insets, rect); + final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0; + if (mlp.topMargin != newMargin) { + mlpChanged = true; + mlp.topMargin = insets.getSystemWindowInsetTop(); + + if (mStatusGuard == null) { + mStatusGuard = new View(mContext); + mStatusGuard.setBackgroundColor(mContext.getColor( + R.color.input_method_navigation_guard)); + addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), + new LayoutParams(LayoutParams.MATCH_PARENT, + mlp.topMargin, Gravity.START | Gravity.TOP)); + } else { + final LayoutParams lp = (LayoutParams) + mStatusGuard.getLayoutParams(); + if (lp.height != mlp.topMargin) { + lp.height = mlp.topMargin; + mStatusGuard.setLayoutParams(lp); + } + } + } + + // The action mode's theme may differ from the app, so + // always show the status guard above it if we have one. + showStatusGuard = mStatusGuard != null; + + // We only need to consume the insets if the action + // mode is overlaid on the app content (e.g. it's + // sitting in a FrameLayout, see + // screen_simple_overlay_action_mode.xml). + final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate() + & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0; + insets = insets.consumeSystemWindowInsets( + false, nonOverlay && showStatusGuard /* top */, false, false); + } else { + // reset top margin + if (mlp.topMargin != 0) { + mlpChanged = true; + mlp.topMargin = 0; + } + } + if (mlpChanged) { + mPrimaryActionModeView.setLayoutParams(mlp); + } + } + } + if (mStatusGuard != null) { + mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); + } + return insets; + } + + private void updateNavigationGuard(WindowInsets insets) { + // IMEs lay out below the nav bar, but the content view must not (for back compat) + if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { + // prevent the content view from including the nav bar height + if (mWindow.mContentParent != null) { + if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) { + MarginLayoutParams mlp = + (MarginLayoutParams) mWindow.mContentParent.getLayoutParams(); + mlp.bottomMargin = insets.getSystemWindowInsetBottom(); + mWindow.mContentParent.setLayoutParams(mlp); + } + } + // position the navigation guard view, creating it if necessary + if (mNavigationGuard == null) { + mNavigationGuard = new View(mContext); + mNavigationGuard.setBackgroundColor(mContext.getColor( + R.color.input_method_navigation_guard)); + addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view), + new LayoutParams(LayoutParams.MATCH_PARENT, + insets.getSystemWindowInsetBottom(), + Gravity.START | Gravity.BOTTOM)); + } else { + LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams(); + lp.height = insets.getSystemWindowInsetBottom(); + mNavigationGuard.setLayoutParams(lp); + } + } + } + + private void drawableChanged() { + if (mChanging) { + return; + } + + setPadding(mFramePadding.left + mBackgroundPadding.left, + mFramePadding.top + mBackgroundPadding.top, + mFramePadding.right + mBackgroundPadding.right, + mFramePadding.bottom + mBackgroundPadding.bottom); + requestLayout(); + invalidate(); + + int opacity = PixelFormat.OPAQUE; + if (windowHasShadow()) { + // If the window has a shadow, it must be translucent. + opacity = PixelFormat.TRANSLUCENT; + } else{ + // Note: If there is no background, we will assume opaque. The + // common case seems to be that an application sets there to be + // no background so it can draw everything itself. For that, + // we would like to assume OPAQUE and let the app force it to + // the slower TRANSLUCENT mode if that is really what it wants. + Drawable bg = getBackground(); + Drawable fg = getForeground(); + if (bg != null) { + if (fg == null) { + opacity = bg.getOpacity(); + } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0 + && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) { + // If the frame padding is zero, then we can be opaque + // if either the frame -or- the background is opaque. + int fop = fg.getOpacity(); + int bop = bg.getOpacity(); + if (false) + Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); + if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { + opacity = PixelFormat.OPAQUE; + } else if (fop == PixelFormat.UNKNOWN) { + opacity = bop; + } else if (bop == PixelFormat.UNKNOWN) { + opacity = fop; + } else { + opacity = Drawable.resolveOpacity(fop, bop); + } + } else { + // For now we have to assume translucent if there is a + // frame with padding... there is no way to tell if the + // frame and background together will draw all pixels. + if (false) + Log.v(TAG, "Padding: " + mFramePadding); + opacity = PixelFormat.TRANSLUCENT; + } + } + if (false) + Log.v(TAG, "Background: " + bg + ", Frame: " + fg); + } + + if (false) + Log.v(TAG, "Selected default opacity: " + opacity); + + mDefaultOpacity = opacity; + if (mFeatureId < 0) { + mWindow.setDefaultWindowFormat(opacity); + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + // If the user is chording a menu shortcut, release the chord since + // this window lost focus + if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus + && mWindow.mPanelChordingKey != 0) { + mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); + } + + final Window.Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { + cb.onWindowFocusChanged(hasWindowFocus); + } + + if (mPrimaryActionMode != null) { + mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus); + } + if (mFloatingActionMode != null) { + mFloatingActionMode.onWindowFocusChanged(hasWindowFocus); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + final Window.Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { + cb.onAttachedToWindow(); + } + + if (mFeatureId == -1) { + /* + * The main window has been attached, try to restore any panels + * that may have been open before. This is called in cases where + * an activity is being killed for configuration change and the + * menu was open. When the activity is recreated, the menu + * should be shown again. + */ + mWindow.openPanelsAfterRestore(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + final Window.Callback cb = mWindow.getCallback(); + if (cb != null && mFeatureId < 0) { + cb.onDetachedFromWindow(); + } + + if (mWindow.mDecorContentParent != null) { + mWindow.mDecorContentParent.dismissPopups(); + } + + if (mPrimaryActionModePopup != null) { + removeCallbacks(mShowPrimaryActionModePopup); + if (mPrimaryActionModePopup.isShowing()) { + mPrimaryActionModePopup.dismiss(); + } + mPrimaryActionModePopup = null; + } + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } + + PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false); + if (st != null && st.menu != null && mFeatureId < 0) { + st.menu.close(); + } + } + + @Override + public void onCloseSystemDialogs(String reason) { + if (mFeatureId >= 0) { + mWindow.closeAllPanels(); + } + } + + public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { + return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null; + } + + public InputQueue.Callback willYouTakeTheInputQueue() { + return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null; + } + + public void setSurfaceType(int type) { + mWindow.setType(type); + } + + public void setSurfaceFormat(int format) { + mWindow.setFormat(format); + } + + public void setSurfaceKeepScreenOn(boolean keepOn) { + if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + @Override + public void onRootViewScrollYChanged(int rootScrollY) { + mRootScrollY = rootScrollY; + updateColorViewTranslations(); + } + + private ActionMode createActionMode( + int type, ActionMode.Callback2 callback, View originatingView) { + switch (type) { + case ActionMode.TYPE_PRIMARY: + default: + return createStandaloneActionMode(callback); + case ActionMode.TYPE_FLOATING: + return createFloatingActionMode(originatingView, callback); + } + } + + private void setHandledActionMode(ActionMode mode) { + if (mode.getType() == ActionMode.TYPE_PRIMARY) { + setHandledPrimaryActionMode(mode); + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + setHandledFloatingActionMode(mode); + } + } + + private ActionMode createStandaloneActionMode(ActionMode.Callback callback) { + endOnGoingFadeAnimation(); + cleanupPrimaryActionMode(); + if (mPrimaryActionModeView == null) { + if (mWindow.isFloating()) { + // Use the action bar theme. + final TypedValue outValue = new TypedValue(); + final Resources.Theme baseTheme = mContext.getTheme(); + baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); + + final Context actionBarContext; + if (outValue.resourceId != 0) { + final Resources.Theme actionBarTheme = mContext.getResources().newTheme(); + actionBarTheme.setTo(baseTheme); + actionBarTheme.applyStyle(outValue.resourceId, true); + + actionBarContext = new ContextThemeWrapper(mContext, 0); + actionBarContext.getTheme().setTo(actionBarTheme); + } else { + actionBarContext = mContext; + } + + mPrimaryActionModeView = new ActionBarContextView(actionBarContext); + mPrimaryActionModePopup = new PopupWindow(actionBarContext, null, + R.attr.actionModePopupWindowStyle); + mPrimaryActionModePopup.setWindowLayoutType( + WindowManager.LayoutParams.TYPE_APPLICATION); + mPrimaryActionModePopup.setContentView(mPrimaryActionModeView); + mPrimaryActionModePopup.setWidth(MATCH_PARENT); + + actionBarContext.getTheme().resolveAttribute( + R.attr.actionBarSize, outValue, true); + final int height = TypedValue.complexToDimensionPixelSize(outValue.data, + actionBarContext.getResources().getDisplayMetrics()); + mPrimaryActionModeView.setContentHeight(height); + mPrimaryActionModePopup.setHeight(WRAP_CONTENT); + mShowPrimaryActionModePopup = new Runnable() { + public void run() { + mPrimaryActionModePopup.showAtLocation( + mPrimaryActionModeView.getApplicationWindowToken(), + Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); + endOnGoingFadeAnimation(); + mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, + 0f, 1f); + mFadeAnim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mPrimaryActionModeView.setVisibility(VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mPrimaryActionModeView.setAlpha(1f); + mFadeAnim = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + mFadeAnim.start(); + } + }; + } else { + ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub); + if (stub != null) { + mPrimaryActionModeView = (ActionBarContextView) stub.inflate(); + } + } + } + if (mPrimaryActionModeView != null) { + mPrimaryActionModeView.killMode(); + ActionMode mode = new StandaloneActionMode( + mPrimaryActionModeView.getContext(), mPrimaryActionModeView, + callback, mPrimaryActionModePopup == null); + return mode; + } + return null; + } + + private void endOnGoingFadeAnimation() { + if (mFadeAnim != null) { + mFadeAnim.end(); + } + } + + private void setHandledPrimaryActionMode(ActionMode mode) { + endOnGoingFadeAnimation(); + mPrimaryActionMode = mode; + mPrimaryActionMode.invalidate(); + mPrimaryActionModeView.initForMode(mPrimaryActionMode); + if (mPrimaryActionModePopup != null) { + post(mShowPrimaryActionModePopup); + } else { + mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f); + mFadeAnim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mPrimaryActionModeView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mPrimaryActionModeView.setAlpha(1f); + mFadeAnim = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + mFadeAnim.start(); + } + mPrimaryActionModeView.sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + private ActionMode createFloatingActionMode( + View originatingView, ActionMode.Callback2 callback) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } + cleanupFloatingActionModeViews(); + final FloatingActionMode mode = + new FloatingActionMode(mContext, callback, originatingView); + mFloatingActionModeOriginatingView = originatingView; + mFloatingToolbarPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mode.updateViewLocationInWindow(); + return true; + } + }; + return mode; + } + + private void setHandledFloatingActionMode(ActionMode mode) { + mFloatingActionMode = mode; + mFloatingToolbar = new FloatingToolbar(mContext, mWindow); + ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar); + mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. + mFloatingActionModeOriginatingView.getViewTreeObserver() + .addOnPreDrawListener(mFloatingToolbarPreDrawListener); + } + + /** + * Informs the decor if a non client decor is attached and visible. + * @param attachedAndVisible true when the decor is visible. + * Note that this will even be called if there is no non client decor. + **/ + void enableNonClientDecor(boolean attachedAndVisible) { + if (mHasNonClientDecor != attachedAndVisible) { + mHasNonClientDecor = attachedAndVisible; + if (getForeground() != null) { + drawableChanged(); + } + } + } + + /** + * Returns true if the window has a non client decor. + * @return If there is a non client decor - even if it is not visible. + **/ + private boolean windowHasNonClientDecor() { + return mHasNonClientDecor; + } + + /** + * Returns true if the Window is free floating and has a shadow (although at some times + * it might not be displaying it, e.g. during a resize). Note that non overlapping windows + * do not have a shadow since it could not be seen anyways (a small screen / tablet + * "tiles" the windows side by side but does not overlap them). + * @return Returns true when the window has a shadow created by the non client decor. + **/ + private boolean windowHasShadow() { + return windowHasNonClientDecor() && ActivityManager.StackId + .hasWindowShadow(mWindow.mWorkspaceId); + } + + void setWindow(PhoneWindow phoneWindow) { + mWindow = phoneWindow; + Context context = getContext(); + if (context instanceof DecorContext) { + DecorContext decorContex = (DecorContext) context; + decorContex.setPhoneWindow(mWindow); + } + } + + private static class ColorViewState { + View view = null; + int targetVisibility = View.INVISIBLE; + boolean present = false; + + final int id; + final int systemUiHideFlag; + final int translucentFlag; + final int verticalGravity; + final int horizontalGravity; + final String transitionName; + final int hideWindowFlag; + + ColorViewState(int systemUiHideFlag, + int translucentFlag, int verticalGravity, int horizontalGravity, + String transitionName, int id, int hideWindowFlag) { + this.id = id; + this.systemUiHideFlag = systemUiHideFlag; + this.translucentFlag = translucentFlag; + this.verticalGravity = verticalGravity; + this.horizontalGravity = horizontalGravity; + this.transitionName = transitionName; + this.hideWindowFlag = hideWindowFlag; + } + } + + /** + * Clears out internal references when the action mode is destroyed. + */ + private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { + private final ActionMode.Callback mWrapped; + + public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { + mWrapped = wrapped; + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return mWrapped.onCreateActionMode(mode, menu); + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + requestFitSystemWindows(); + return mWrapped.onPrepareActionMode(mode, menu); + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return mWrapped.onActionItemClicked(mode, item); + } + + public void onDestroyActionMode(ActionMode mode) { + mWrapped.onDestroyActionMode(mode); + final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.M; + final boolean isPrimary; + final boolean isFloating; + if (isMncApp) { + isPrimary = mode == mPrimaryActionMode; + isFloating = mode == mFloatingActionMode; + if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) { + Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; " + + mode + " was not the current primary action mode! Expected " + + mPrimaryActionMode); + } + if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) { + Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; " + + mode + " was not the current floating action mode! Expected " + + mFloatingActionMode); + } + } else { + isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY; + isFloating = mode.getType() == ActionMode.TYPE_FLOATING; + } + if (isPrimary) { + if (mPrimaryActionModePopup != null) { + removeCallbacks(mShowPrimaryActionModePopup); + } + if (mPrimaryActionModeView != null) { + endOnGoingFadeAnimation(); + mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, + 1f, 0f); + mFadeAnim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + mPrimaryActionModeView.setVisibility(GONE); + if (mPrimaryActionModePopup != null) { + mPrimaryActionModePopup.dismiss(); + } + mPrimaryActionModeView.removeAllViews(); + mFadeAnim = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + mFadeAnim.start(); + } + + mPrimaryActionMode = null; + } else if (isFloating) { + cleanupFloatingActionModeViews(); + mFloatingActionMode = null; + } + if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { + try { + mWindow.getCallback().onActionModeFinished(mode); + } catch (AbstractMethodError ame) { + // Older apps might not implement this callback method. + } + } + requestFitSystemWindows(); + } + + @Override + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + if (mWrapped instanceof ActionMode.Callback2) { + ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); + } else { + super.onGetContentRect(mode, view, outRect); + } + } + } +} diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 83f810fa76e5..c42714941a5b 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -18,22 +18,15 @@ package com.android.internal.policy; 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; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 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; import android.os.UserHandle; -import android.view.ActionMode; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.IRotationWatcher.Stub; @@ -55,15 +48,9 @@ import android.view.ViewGroup; import android.view.ViewManager; import android.view.ViewParent; import android.view.ViewRootImpl; -import android.view.ViewStub; -import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.Window; -import android.view.WindowInsets; import android.view.WindowManager; import com.android.internal.R; -import com.android.internal.view.FloatingActionMode; -import com.android.internal.view.RootViewSurfaceTaker; -import com.android.internal.view.StandaloneActionMode; import com.android.internal.view.menu.ContextMenuBuilder; import com.android.internal.view.menu.IconMenuPresenter; import com.android.internal.view.menu.ListMenuPresenter; @@ -72,10 +59,7 @@ import com.android.internal.view.menu.MenuDialogHelper; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.MenuPresenter; import com.android.internal.view.menu.MenuView; -import com.android.internal.widget.ActionBarContextView; -import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorContentParent; -import com.android.internal.widget.FloatingToolbar; import com.android.internal.widget.NonClientDecorView; import com.android.internal.widget.SwipeDismissLayout; @@ -87,9 +71,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.content.res.TypedArray; -import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -108,22 +90,17 @@ import android.transition.TransitionInflater; import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AndroidRuntimeException; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ImageView; -import android.widget.PopupWindow; import android.widget.ProgressBar; import android.widget.TextView; @@ -142,8 +119,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private final static String TAG = "PhoneWindow"; - private final static boolean SWEEP_OPEN_MENU = false; - private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300; private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES | @@ -185,21 +160,19 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. - private ViewGroup mContentParent; - - private ViewGroup mContentRoot; + ViewGroup mContentParent; Callback2 mTakeSurfaceCallback; InputQueue.Callback mTakeInputQueueCallback; - private boolean mIsFloating; + boolean mIsFloating; private LayoutInflater mLayoutInflater; private TextView mTitleView; - private DecorContentParent mDecorContentParent; + DecorContentParent mDecorContentParent; private ActionMenuPresenterCallback mActionMenuPresenterCallback; private PanelMenuPresenterCallback mPanelMenuPresenterCallback; @@ -231,13 +204,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * multiple panels). Shortcuts will go to this panel. It gets set in * {@link #preparePanel} and cleared in {@link #closePanel}. */ - private PanelFeatureState mPreparedPanel; + PanelFeatureState mPreparedPanel; /** * The keycode that is currently held down (as a modifier) for chording. If * this is 0, there is no key held down. */ - private int mPanelChordingKey; + int mPanelChordingKey; private ImageView mLeftIconView; @@ -261,8 +234,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private int mFrameResource = 0; private int mTextColor = 0; - private int mStatusBarColor = 0; - private int mNavigationBarColor = 0; + int mStatusBarColor = 0; + int mNavigationBarColor = 0; private boolean mForcedStatusBarColor = false; private boolean mForcedNavigationBarColor = false; @@ -272,9 +245,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mAlwaysReadCloseOnTouchAttr = false; - private ContextMenuBuilder mContextMenu; - private MenuDialogHelper mContextMenuHelper; - private MenuPopupHelper mContextMenuPopupHelper; + ContextMenuBuilder mContextMenu; + MenuDialogHelper mContextMenuHelper; + MenuPopupHelper mContextMenuPopupHelper; private boolean mClosingActionMenu; private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; @@ -312,9 +285,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private long mBackgroundFadeDurationMillis = -1; private Boolean mSharedElementsUseOverlay; - private Rect mTempRect; - private Rect mOutsets = new Rect(); - private boolean mIsStartingWindow; private int mTheme = -1; @@ -1170,7 +1140,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags); } - private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, + boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, int flags) { if (event.isSystem() || (st == null)) { return false; @@ -2261,7 +2231,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * called sometime after {@link #restorePanelState} when it is safe to add * to the window manager. */ - private void openPanelsAfterRestore() { + void openPanelsAfterRestore() { PanelFeatureState[] panels = mPanels; if (panels == null) { @@ -2332,1530 +2302,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker { - - /* package */int mDefaultOpacity = PixelFormat.OPAQUE; - - /** The feature ID of the panel, or -1 if this is the application's DecorView */ - private final int mFeatureId; - - private final Rect mDrawingBounds = new Rect(); - - private final Rect mBackgroundPadding = new Rect(); - - private final Rect mFramePadding = new Rect(); - - private final Rect mFrameOffsets = new Rect(); - - // True if a non client area decor exists. - private boolean mHasNonClientDecor = false; - - private boolean mChanging; - - private Drawable mMenuBackground; - private boolean mWatchingForMenu; - private int mDownY; - - private ActionMode mPrimaryActionMode; - private ActionMode mFloatingActionMode; - private ActionBarContextView mPrimaryActionModeView; - private PopupWindow mPrimaryActionModePopup; - private Runnable mShowPrimaryActionModePopup; - private OnPreDrawListener mFloatingToolbarPreDrawListener; - private View mFloatingActionModeOriginatingView; - private FloatingToolbar mFloatingToolbar; - private ObjectAnimator mFadeAnim; - - // View added at runtime to draw under the status bar area - private View mStatusGuard; - // View added at runtime to draw under the navigation bar area - private View mNavigationGuard; - - private final ColorViewState mStatusColorViewState = new ColorViewState( - SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, - Gravity.TOP, - Gravity.LEFT, - STATUS_BAR_BACKGROUND_TRANSITION_NAME, - com.android.internal.R.id.statusBarBackground, - FLAG_FULLSCREEN); - private final ColorViewState mNavigationColorViewState = new ColorViewState( - SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, - Gravity.BOTTOM, - Gravity.RIGHT, - NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, - com.android.internal.R.id.navigationBarBackground, - 0 /* hideWindowFlag */); - - private final Interpolator mShowInterpolator; - private final Interpolator mHideInterpolator; - private final int mBarEnterExitDuration; - - private final BackgroundFallback mBackgroundFallback = new BackgroundFallback(); - - private int mLastTopInset = 0; - private int mLastBottomInset = 0; - private int mLastRightInset = 0; - private boolean mLastHasTopStableInset = false; - private boolean mLastHasBottomStableInset = false; - private boolean mLastHasRightStableInset = false; - private int mLastWindowFlags = 0; - - private int mRootScrollY = 0; - - private PhoneWindow mWindow; - - private DecorView(Context context, int featureId, PhoneWindow window) { - super(context); - mFeatureId = featureId; - - mShowInterpolator = AnimationUtils.loadInterpolator(context, - android.R.interpolator.linear_out_slow_in); - mHideInterpolator = AnimationUtils.loadInterpolator(context, - android.R.interpolator.fast_out_linear_in); - - mBarEnterExitDuration = context.getResources().getInteger( - R.integer.dock_enter_exit_duration); - - setWindow(window); - } - - public void setBackgroundFallback(int resId) { - mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null); - setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback()); - } - - @Override - public void onDraw(Canvas c) { - super.onDraw(c); - mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - final int keyCode = event.getKeyCode(); - final int action = event.getAction(); - final boolean isDown = action == KeyEvent.ACTION_DOWN; - - if (isDown && (event.getRepeatCount() == 0)) { - // First handle chording of panel key: if a panel key is held - // but not released, try to execute a shortcut in it. - if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { - boolean handled = dispatchKeyShortcutEvent(event); - if (handled) { - return true; - } - } - - // If a panel is open, perform a shortcut on it without the - // chorded panel key - if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { - if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { - return true; - } - } - } - - if (!mWindow.isDestroyed()) { - final Callback cb = mWindow.getCallback(); - final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) - : super.dispatchKeyEvent(event); - if (handled) { - return true; - } - } - - return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) - : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); - } - - @Override - public boolean dispatchKeyShortcutEvent(KeyEvent ev) { - // If the panel is already prepared, then perform the shortcut using it. - boolean handled; - if (mWindow.mPreparedPanel != null) { - handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev, - Menu.FLAG_PERFORM_NO_CLOSE); - if (handled) { - if (mWindow.mPreparedPanel != null) { - mWindow.mPreparedPanel.isHandled = true; - } - return true; - } - } - - // Shortcut not handled by the panel. Dispatch to the view hierarchy. - final Callback cb = mWindow.getCallback(); - handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0 - ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev); - if (handled) { - return true; - } - - // If the panel is not prepared, then we may be trying to handle a shortcut key - // combination such as Control+C. Temporarily prepare the panel then mark it - // unprepared again when finished to ensure that the panel will again be prepared - // the next time it is shown for real. - PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false); - if (st != null && mWindow.mPreparedPanel == null) { - mWindow.preparePanel(st, ev); - handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev, - Menu.FLAG_PERFORM_NO_CLOSE); - st.isPrepared = false; - if (handled) { - return true; - } - } - return false; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - final Callback cb = mWindow.getCallback(); - return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 - ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); - } - - @Override - public boolean dispatchTrackballEvent(MotionEvent ev) { - final Callback cb = mWindow.getCallback(); - return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 - ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent ev) { - final Callback cb = mWindow.getCallback(); - return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 - ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev); - } - - public boolean superDispatchKeyEvent(KeyEvent event) { - // Give priority to closing action modes if applicable. - if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - final int action = event.getAction(); - // Back cancels action modes first. - if (mPrimaryActionMode != null) { - if (action == KeyEvent.ACTION_UP) { - mPrimaryActionMode.finish(); - } - return true; - } - } - - return super.dispatchKeyEvent(event); - } - - public boolean superDispatchKeyShortcutEvent(KeyEvent event) { - return super.dispatchKeyShortcutEvent(event); - } - - public boolean superDispatchTouchEvent(MotionEvent event) { - return super.dispatchTouchEvent(event); - } - - public boolean superDispatchTrackballEvent(MotionEvent event) { - return super.dispatchTrackballEvent(event); - } - - public boolean superDispatchGenericMotionEvent(MotionEvent event) { - return super.dispatchGenericMotionEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - return onInterceptTouchEvent(event); - } - - private boolean isOutOfInnerBounds(int x, int y) { - return x < 0 || y < 0 || x > getWidth() || y > getHeight(); - } - - private boolean isOutOfBounds(int x, int y) { - return x < -5 || y < -5 || x > (getWidth() + 5) - || y > (getHeight() + 5); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - int action = event.getAction(); - if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) { - // Don't dispatch ACTION_DOWN to the non client decor if the window is - // resizable and the event was (starting) outside the window. - // Window resizing events should be handled by WindowManager. - // TODO: Investigate how to handle the outside touch in window manager - // without generating these events. - // Currently we receive these because we need to enlarge the window's - // touch region so that the monitor channel receives the events - // in the outside touch area. - if (action == MotionEvent.ACTION_DOWN) { - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if (isOutOfInnerBounds(x, y)) { - return true; - } - } - } - - if (mFeatureId >= 0) { - if (action == MotionEvent.ACTION_DOWN) { - int x = (int)event.getX(); - int y = (int)event.getY(); - if (isOutOfBounds(x, y)) { - mWindow.closePanel(mFeatureId); - return true; - } - } - } - - if (!SWEEP_OPEN_MENU) { - return false; - } - - if (mFeatureId >= 0) { - if (action == MotionEvent.ACTION_DOWN) { - Log.i(TAG, "Watchiing!"); - mWatchingForMenu = true; - mDownY = (int) event.getY(); - return false; - } - - if (!mWatchingForMenu) { - return false; - } - - int y = (int)event.getY(); - if (action == MotionEvent.ACTION_MOVE) { - if (y > (mDownY+30)) { - Log.i(TAG, "Closing!"); - mWindow.closePanel(mFeatureId); - mWatchingForMenu = false; - return true; - } - } else if (action == MotionEvent.ACTION_UP) { - mWatchingForMenu = false; - } - - return false; - } - - //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() - // + " (in " + getHeight() + ")"); - - if (action == MotionEvent.ACTION_DOWN) { - int y = (int)event.getY(); - if (y >= (getHeight()-5) && !mWindow.hasChildren()) { - Log.i(TAG, "Watching!"); - mWatchingForMenu = true; - } - return false; - } - - if (!mWatchingForMenu) { - return false; - } - - int y = (int)event.getY(); - if (action == MotionEvent.ACTION_MOVE) { - if (y < (getHeight()-30)) { - Log.i(TAG, "Opening!"); - mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); - mWatchingForMenu = false; - return true; - } - } else if (action == MotionEvent.ACTION_UP) { - mWatchingForMenu = false; - } - - return false; - } - - @Override - public void sendAccessibilityEvent(int eventType) { - if (!AccessibilityManager.getInstance(mContext).isEnabled()) { - return; - } - - // if we are showing a feature that should be announced and one child - // make this child the event source since this is the feature itself - // otherwise the callback will take over and announce its client - if ((mFeatureId == FEATURE_OPTIONS_PANEL || - mFeatureId == FEATURE_CONTEXT_MENU || - mFeatureId == FEATURE_PROGRESS || - mFeatureId == FEATURE_INDETERMINATE_PROGRESS) - && getChildCount() == 1) { - getChildAt(0).sendAccessibilityEvent(eventType); - } else { - super.sendAccessibilityEvent(eventType); - } - } - - @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - final Callback cb = mWindow.getCallback(); - if (cb != null && !mWindow.isDestroyed()) { - if (cb.dispatchPopulateAccessibilityEvent(event)) { - return true; - } - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } - - @Override - protected boolean setFrame(int l, int t, int r, int b) { - boolean changed = super.setFrame(l, t, r, b); - if (changed) { - final Rect drawingBounds = mDrawingBounds; - getDrawingRect(drawingBounds); - - Drawable fg = getForeground(); - if (fg != null) { - final Rect frameOffsets = mFrameOffsets; - drawingBounds.left += frameOffsets.left; - drawingBounds.top += frameOffsets.top; - drawingBounds.right -= frameOffsets.right; - drawingBounds.bottom -= frameOffsets.bottom; - fg.setBounds(drawingBounds); - final Rect framePadding = mFramePadding; - drawingBounds.left += framePadding.left - frameOffsets.left; - drawingBounds.top += framePadding.top - frameOffsets.top; - drawingBounds.right -= framePadding.right - frameOffsets.right; - drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; - } - - Drawable bg = getBackground(); - if (bg != null) { - bg.setBounds(drawingBounds); - } - - if (SWEEP_OPEN_MENU) { - if (mMenuBackground == null && mFeatureId < 0 - && mWindow.getAttributes().height - == WindowManager.LayoutParams.MATCH_PARENT) { - mMenuBackground = getContext().getDrawable( - R.drawable.menu_background); - } - if (mMenuBackground != null) { - mMenuBackground.setBounds(drawingBounds.left, - drawingBounds.bottom-6, drawingBounds.right, - drawingBounds.bottom+20); - } - } - } - return changed; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; - - final int widthMode = getMode(widthMeasureSpec); - final int heightMode = getMode(heightMeasureSpec); - - boolean fixedWidth = false; - if (widthMode == AT_MOST) { - final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor - : mWindow.mFixedWidthMajor; - if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { - final int w; - if (tvw.type == TypedValue.TYPE_DIMENSION) { - w = (int) tvw.getDimension(metrics); - } else if (tvw.type == TypedValue.TYPE_FRACTION) { - w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); - } else { - w = 0; - } - - if (w > 0) { - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - widthMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(w, widthSize), EXACTLY); - fixedWidth = true; - } - } - } - - if (heightMode == AT_MOST) { - final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor - : mWindow.mFixedHeightMinor; - if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { - final int h; - if (tvh.type == TypedValue.TYPE_DIMENSION) { - h = (int) tvh.getDimension(metrics); - } else if (tvh.type == TypedValue.TYPE_FRACTION) { - h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); - } else { - h = 0; - } - if (h > 0) { - final int heightSize = MeasureSpec.getSize(heightMeasureSpec); - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(h, heightSize), EXACTLY); - } - } - } - - getOutsets(mWindow.mOutsets); - if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) { - int mode = MeasureSpec.getMode(heightMeasureSpec); - if (mode != MeasureSpec.UNSPECIFIED) { - int height = MeasureSpec.getSize(heightMeasureSpec); - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode); - } - } - if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) { - int mode = MeasureSpec.getMode(widthMeasureSpec); - if (mode != MeasureSpec.UNSPECIFIED) { - int width = MeasureSpec.getSize(widthMeasureSpec); - widthMeasureSpec = MeasureSpec.makeMeasureSpec( - width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode); - } - } - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int width = getMeasuredWidth(); - boolean measure = false; - - widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); - - if (!fixedWidth && widthMode == AT_MOST) { - final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; - if (tv.type != TypedValue.TYPE_NULL) { - final int min; - if (tv.type == TypedValue.TYPE_DIMENSION) { - min = (int)tv.getDimension(metrics); - } else if (tv.type == TypedValue.TYPE_FRACTION) { - min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); - } else { - min = 0; - } - - if (width < min) { - widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); - measure = true; - } - } - } - - // TODO: Support height? - - if (measure) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - getOutsets(mWindow.mOutsets); - if (mWindow.mOutsets.left > 0) { - offsetLeftAndRight(-mWindow.mOutsets.left); - } - if (mWindow.mOutsets.top > 0) { - offsetTopAndBottom(-mWindow.mOutsets.top); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - if (mMenuBackground != null) { - mMenuBackground.draw(canvas); - } - } - - @Override - public boolean showContextMenuForChild(View originalView) { - // Reuse the context menu builder - if (mWindow.mContextMenu == null) { - mWindow.mContextMenu = new ContextMenuBuilder(getContext()); - mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback); - } else { - mWindow.mContextMenu.clearAll(); - } - - final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView, - originalView.getWindowToken()); - if (helper != null) { - helper.setPresenterCallback(mWindow.mContextMenuCallback); - } else if (mWindow.mContextMenuHelper != null) { - // No menu to show, but if we have a menu currently showing it just became blank. - // Close it. - mWindow.mContextMenuHelper.dismiss(); - } - mWindow.mContextMenuHelper = helper; - return helper != null; - } - - @Override - public boolean showContextMenuForChild(View originalView, float x, float y) { - // Reuse the context menu builder - if (mWindow.mContextMenu == null) { - mWindow.mContextMenu = new ContextMenuBuilder(getContext()); - mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback); - } else { - mWindow.mContextMenu.clearAll(); - } - - final MenuPopupHelper helper = mWindow.mContextMenu.showPopup( - getContext(), originalView, x, y); - if (helper != null) { - helper.setCallback(mWindow.mContextMenuCallback); - } else if (mWindow.mContextMenuPopupHelper != null) { - // No menu to show, but if we have a menu currently showing it just became blank. - // Close it. - mWindow.mContextMenuPopupHelper.dismiss(); - } - mWindow.mContextMenuPopupHelper = helper; - return helper != null; - } - - @Override - public ActionMode startActionModeForChild(View originalView, - ActionMode.Callback callback) { - return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY); - } - - @Override - public ActionMode startActionModeForChild( - View child, ActionMode.Callback callback, int type) { - return startActionMode(child, callback, type); - } - - @Override - public ActionMode startActionMode(ActionMode.Callback callback) { - return startActionMode(callback, ActionMode.TYPE_PRIMARY); - } - - @Override - public ActionMode startActionMode(ActionMode.Callback callback, int type) { - return startActionMode(this, callback, type); - } - - private ActionMode startActionMode( - View originatingView, ActionMode.Callback callback, int type) { - ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); - ActionMode mode = null; - if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { - try { - mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type); - } catch (AbstractMethodError ame) { - // Older apps might not implement the typed version of this method. - if (type == ActionMode.TYPE_PRIMARY) { - try { - mode = mWindow.getCallback().onWindowStartingActionMode( - wrappedCallback); - } catch (AbstractMethodError ame2) { - // Older apps might not implement this callback method at all. - } - } - } - } - if (mode != null) { - if (mode.getType() == ActionMode.TYPE_PRIMARY) { - cleanupPrimaryActionMode(); - mPrimaryActionMode = mode; - } else if (mode.getType() == ActionMode.TYPE_FLOATING) { - if (mFloatingActionMode != null) { - mFloatingActionMode.finish(); - } - mFloatingActionMode = mode; - } - } else { - mode = createActionMode(type, wrappedCallback, originatingView); - if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { - setHandledActionMode(mode); - } else { - mode = null; - } - } - if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) { - try { - mWindow.getCallback().onActionModeStarted(mode); - } catch (AbstractMethodError ame) { - // Older apps might not implement this callback method. - } - } - return mode; - } - - private void cleanupPrimaryActionMode() { - if (mPrimaryActionMode != null) { - mPrimaryActionMode.finish(); - mPrimaryActionMode = null; - } - if (mPrimaryActionModeView != null) { - mPrimaryActionModeView.killMode(); - } - } - - private void cleanupFloatingActionModeViews() { - if (mFloatingToolbar != null) { - mFloatingToolbar.dismiss(); - mFloatingToolbar = null; - } - if (mFloatingActionModeOriginatingView != null) { - if (mFloatingToolbarPreDrawListener != null) { - mFloatingActionModeOriginatingView.getViewTreeObserver() - .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); - mFloatingToolbarPreDrawListener = null; - } - mFloatingActionModeOriginatingView = null; - } - } - - public void startChanging() { - mChanging = true; - } - - public void finishChanging() { - mChanging = false; - drawableChanged(); - } - - public void setWindowBackground(Drawable drawable) { - if (getBackground() != drawable) { - setBackgroundDrawable(drawable); - if (drawable != null) { - drawable.getPadding(mBackgroundPadding); - } else { - mBackgroundPadding.setEmpty(); - } - drawableChanged(); - } - } - - public void setWindowFrame(Drawable drawable) { - if (getForeground() != drawable) { - setForeground(drawable); - if (drawable != null) { - drawable.getPadding(mFramePadding); - } else { - mFramePadding.setEmpty(); - } - drawableChanged(); - } - } - - @Override - public void onWindowSystemUiVisibilityChanged(int visible) { - updateColorViews(null /* insets */, true /* animate */); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mFrameOffsets.set(insets.getSystemWindowInsets()); - insets = updateColorViews(insets, true /* animate */); - insets = updateStatusGuard(insets); - updateNavigationGuard(insets); - if (getForeground() != null) { - drawableChanged(); - } - return insets; - } - - @Override - public boolean isTransitionGroup() { - return false; - } - - private WindowInsets updateColorViews(WindowInsets insets, boolean animate) { - WindowManager.LayoutParams attrs = mWindow.getAttributes(); - int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); - - if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) { - boolean disallowAnimate = !isLaidOut(); - disallowAnimate |= ((mLastWindowFlags ^ attrs.flags) - & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; - mLastWindowFlags = attrs.flags; - - if (insets != null) { - mLastTopInset = Math.min(insets.getStableInsetTop(), - insets.getSystemWindowInsetTop()); - mLastBottomInset = Math.min(insets.getStableInsetBottom(), - insets.getSystemWindowInsetBottom()); - mLastRightInset = Math.min(insets.getStableInsetRight(), - insets.getSystemWindowInsetRight()); - - // Don't animate if the presence of stable insets has changed, because that - // indicates that the window was either just added and received them for the - // first time, or the window size or position has changed. - boolean hasTopStableInset = insets.getStableInsetTop() != 0; - disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); - mLastHasTopStableInset = hasTopStableInset; - - boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; - disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); - mLastHasBottomStableInset = hasBottomStableInset; - - boolean hasRightStableInset = insets.getStableInsetRight() != 0; - disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); - mLastHasRightStableInset = hasRightStableInset; - } - - boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0; - int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset; - updateColorViewInt(mNavigationColorViewState, sysUiVisibility, - mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge, - 0 /* rightInset */, animate && !disallowAnimate); - - boolean statusBarNeedsRightInset = navBarToRightEdge - && mNavigationColorViewState.present; - int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0; - updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor, - mLastTopInset, false /* matchVertical */, statusBarRightInset, - animate && !disallowAnimate); - } - - // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need - // to ensure that the rest of the view hierarchy doesn't notice it, unless they've - // explicitly asked for it. - - boolean consumingNavBar = - (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 - && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 - && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; - - int consumedRight = consumingNavBar ? mLastRightInset : 0; - int consumedBottom = consumingNavBar ? mLastBottomInset : 0; - - if (mWindow.mContentRoot != null - && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { - MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams(); - if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) { - lp.rightMargin = consumedRight; - lp.bottomMargin = consumedBottom; - mWindow.mContentRoot.setLayoutParams(lp); - - if (insets == null) { - // The insets have changed, but we're not currently in the process - // of dispatching them. - requestApplyInsets(); - } - } - if (insets != null) { - insets = insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight() - consumedRight, - insets.getSystemWindowInsetBottom() - consumedBottom); - } - } - - if (insets != null) { - insets = insets.consumeStableInsets(); - } - return insets; - } - - /** - * Update a color view - * - * @param state the color view to update. - * @param sysUiVis the current systemUiVisibility to apply. - * @param color the current color to apply. - * @param size the current size in the non-parent-matching dimension. - * @param verticalBar if true the view is attached to a vertical edge, otherwise to a - * horizontal edge, - * @param rightMargin rightMargin for the color view. - * @param animate if true, the change will be animated. - */ - private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, - int size, boolean verticalBar, int rightMargin, boolean animate) { - state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0 - && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0 - && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; - boolean show = state.present - && (color & Color.BLACK) != 0 - && (mWindow.getAttributes().flags & state.translucentFlag) == 0; - - boolean visibilityChanged = false; - View view = state.view; - - int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; - int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; - int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity; - - if (view == null) { - if (show) { - state.view = view = new View(mContext); - view.setBackgroundColor(color); - view.setTransitionName(state.transitionName); - view.setId(state.id); - visibilityChanged = true; - view.setVisibility(INVISIBLE); - state.targetVisibility = VISIBLE; - - LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, - resolvedGravity); - lp.rightMargin = rightMargin; - addView(view, lp); - updateColorViewTranslations(); - } - } else { - int vis = show ? VISIBLE : INVISIBLE; - visibilityChanged = state.targetVisibility != vis; - state.targetVisibility = vis; - LayoutParams lp = (LayoutParams) view.getLayoutParams(); - if (lp.height != resolvedHeight || lp.width != resolvedWidth - || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) { - lp.height = resolvedHeight; - lp.width = resolvedWidth; - lp.gravity = resolvedGravity; - lp.rightMargin = rightMargin; - view.setLayoutParams(lp); - } - if (show) { - view.setBackgroundColor(color); - } - } - if (visibilityChanged) { - view.animate().cancel(); - if (animate) { - if (show) { - if (view.getVisibility() != VISIBLE) { - view.setVisibility(VISIBLE); - view.setAlpha(0.0f); - } - view.animate().alpha(1.0f).setInterpolator(mShowInterpolator). - setDuration(mBarEnterExitDuration); - } else { - view.animate().alpha(0.0f).setInterpolator(mHideInterpolator) - .setDuration(mBarEnterExitDuration) - .withEndAction(new Runnable() { - @Override - public void run() { - state.view.setAlpha(1.0f); - state.view.setVisibility(INVISIBLE); - } - }); - } - } else { - view.setAlpha(1.0f); - view.setVisibility(show ? VISIBLE : INVISIBLE); - } - } - } - - private void updateColorViewTranslations() { - // Put the color views back in place when they get moved off the screen - // due to the the ViewRootImpl panning. - int rootScrollY = mRootScrollY; - if (mStatusColorViewState.view != null) { - mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0); - } - if (mNavigationColorViewState.view != null) { - mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0); - } - } - - private WindowInsets updateStatusGuard(WindowInsets insets) { - boolean showStatusGuard = false; - // Show the status guard when the non-overlay contextual action bar is showing - if (mPrimaryActionModeView != null) { - if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) { - // Insets are magic! - final MarginLayoutParams mlp = (MarginLayoutParams) - mPrimaryActionModeView.getLayoutParams(); - boolean mlpChanged = false; - if (mPrimaryActionModeView.isShown()) { - if (mWindow.mTempRect == null) { - mWindow.mTempRect = new Rect(); - } - final Rect rect = mWindow.mTempRect; - - // If the parent doesn't consume the insets, manually - // apply the default system window insets. - mWindow.mContentParent.computeSystemWindowInsets(insets, rect); - final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0; - if (mlp.topMargin != newMargin) { - mlpChanged = true; - mlp.topMargin = insets.getSystemWindowInsetTop(); - - if (mStatusGuard == null) { - mStatusGuard = new View(mContext); - mStatusGuard.setBackgroundColor(mContext.getColor( - R.color.input_method_navigation_guard)); - addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), - new LayoutParams(LayoutParams.MATCH_PARENT, - mlp.topMargin, Gravity.START | Gravity.TOP)); - } else { - final LayoutParams lp = (LayoutParams) - mStatusGuard.getLayoutParams(); - if (lp.height != mlp.topMargin) { - lp.height = mlp.topMargin; - mStatusGuard.setLayoutParams(lp); - } - } - } - - // The action mode's theme may differ from the app, so - // always show the status guard above it if we have one. - showStatusGuard = mStatusGuard != null; - - // We only need to consume the insets if the action - // mode is overlaid on the app content (e.g. it's - // sitting in a FrameLayout, see - // screen_simple_overlay_action_mode.xml). - final boolean nonOverlay = (mWindow.getLocalFeatures() - & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0; - insets = insets.consumeSystemWindowInsets( - false, nonOverlay && showStatusGuard /* top */, false, false); - } else { - // reset top margin - if (mlp.topMargin != 0) { - mlpChanged = true; - mlp.topMargin = 0; - } - } - if (mlpChanged) { - mPrimaryActionModeView.setLayoutParams(mlp); - } - } - } - if (mStatusGuard != null) { - mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); - } - return insets; - } - - private void updateNavigationGuard(WindowInsets insets) { - // IMEs lay out below the nav bar, but the content view must not (for back compat) - if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { - // prevent the content view from including the nav bar height - if (mWindow.mContentParent != null) { - if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) { - MarginLayoutParams mlp = - (MarginLayoutParams) mWindow.mContentParent.getLayoutParams(); - mlp.bottomMargin = insets.getSystemWindowInsetBottom(); - mWindow.mContentParent.setLayoutParams(mlp); - } - } - // position the navigation guard view, creating it if necessary - if (mNavigationGuard == null) { - mNavigationGuard = new View(mContext); - mNavigationGuard.setBackgroundColor(mContext.getColor( - R.color.input_method_navigation_guard)); - addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view), - new LayoutParams(LayoutParams.MATCH_PARENT, - insets.getSystemWindowInsetBottom(), - Gravity.START | Gravity.BOTTOM)); - } else { - LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams(); - lp.height = insets.getSystemWindowInsetBottom(); - mNavigationGuard.setLayoutParams(lp); - } - } - } - - private void drawableChanged() { - if (mChanging) { - return; - } - - setPadding(mFramePadding.left + mBackgroundPadding.left, - mFramePadding.top + mBackgroundPadding.top, - mFramePadding.right + mBackgroundPadding.right, - mFramePadding.bottom + mBackgroundPadding.bottom); - requestLayout(); - invalidate(); - - int opacity = PixelFormat.OPAQUE; - if (windowHasShadow()) { - // If the window has a shadow, it must be translucent. - opacity = PixelFormat.TRANSLUCENT; - } else{ - // Note: If there is no background, we will assume opaque. The - // common case seems to be that an application sets there to be - // no background so it can draw everything itself. For that, - // we would like to assume OPAQUE and let the app force it to - // the slower TRANSLUCENT mode if that is really what it wants. - Drawable bg = getBackground(); - Drawable fg = getForeground(); - if (bg != null) { - if (fg == null) { - opacity = bg.getOpacity(); - } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0 - && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) { - // If the frame padding is zero, then we can be opaque - // if either the frame -or- the background is opaque. - int fop = fg.getOpacity(); - int bop = bg.getOpacity(); - if (false) - Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); - if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { - opacity = PixelFormat.OPAQUE; - } else if (fop == PixelFormat.UNKNOWN) { - opacity = bop; - } else if (bop == PixelFormat.UNKNOWN) { - opacity = fop; - } else { - opacity = Drawable.resolveOpacity(fop, bop); - } - } else { - // For now we have to assume translucent if there is a - // frame with padding... there is no way to tell if the - // frame and background together will draw all pixels. - if (false) - Log.v(TAG, "Padding: " + mFramePadding); - opacity = PixelFormat.TRANSLUCENT; - } - } - if (false) - Log.v(TAG, "Background: " + bg + ", Frame: " + fg); - } - - if (false) - Log.v(TAG, "Selected default opacity: " + opacity); - - mDefaultOpacity = opacity; - if (mFeatureId < 0) { - mWindow.setDefaultWindowFormat(opacity); - } - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - - // If the user is chording a menu shortcut, release the chord since - // this window lost focus - if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus - && mWindow.mPanelChordingKey != 0) { - mWindow.closePanel(FEATURE_OPTIONS_PANEL); - } - - final Callback cb = mWindow.getCallback(); - if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { - cb.onWindowFocusChanged(hasWindowFocus); - } - - if (mPrimaryActionMode != null) { - mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus); - } - if (mFloatingActionMode != null) { - mFloatingActionMode.onWindowFocusChanged(hasWindowFocus); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - final Callback cb = mWindow.getCallback(); - if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { - cb.onAttachedToWindow(); - } - - if (mFeatureId == -1) { - /* - * The main window has been attached, try to restore any panels - * that may have been open before. This is called in cases where - * an activity is being killed for configuration change and the - * menu was open. When the activity is recreated, the menu - * should be shown again. - */ - mWindow.openPanelsAfterRestore(); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - final Callback cb = mWindow.getCallback(); - if (cb != null && mFeatureId < 0) { - cb.onDetachedFromWindow(); - } - - if (mWindow.mDecorContentParent != null) { - mWindow.mDecorContentParent.dismissPopups(); - } - - if (mPrimaryActionModePopup != null) { - removeCallbacks(mShowPrimaryActionModePopup); - if (mPrimaryActionModePopup.isShowing()) { - mPrimaryActionModePopup.dismiss(); - } - mPrimaryActionModePopup = null; - } - if (mFloatingToolbar != null) { - mFloatingToolbar.dismiss(); - mFloatingToolbar = null; - } - - PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false); - if (st != null && st.menu != null && mFeatureId < 0) { - st.menu.close(); - } - } - - @Override - public void onCloseSystemDialogs(String reason) { - if (mFeatureId >= 0) { - mWindow.closeAllPanels(); - } - } - - public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { - return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null; - } - - public InputQueue.Callback willYouTakeTheInputQueue() { - return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null; - } - - public void setSurfaceType(int type) { - mWindow.setType(type); - } - - public void setSurfaceFormat(int format) { - mWindow.setFormat(format); - } - - public void setSurfaceKeepScreenOn(boolean keepOn) { - if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - @Override - public void onRootViewScrollYChanged(int rootScrollY) { - mRootScrollY = rootScrollY; - updateColorViewTranslations(); - } - - private ActionMode createActionMode( - int type, ActionMode.Callback2 callback, View originatingView) { - switch (type) { - case ActionMode.TYPE_PRIMARY: - default: - return createStandaloneActionMode(callback); - case ActionMode.TYPE_FLOATING: - return createFloatingActionMode(originatingView, callback); - } - } - - private void setHandledActionMode(ActionMode mode) { - if (mode.getType() == ActionMode.TYPE_PRIMARY) { - setHandledPrimaryActionMode(mode); - } else if (mode.getType() == ActionMode.TYPE_FLOATING) { - setHandledFloatingActionMode(mode); - } - } - - private ActionMode createStandaloneActionMode(ActionMode.Callback callback) { - endOnGoingFadeAnimation(); - cleanupPrimaryActionMode(); - if (mPrimaryActionModeView == null) { - if (mWindow.isFloating()) { - // Use the action bar theme. - final TypedValue outValue = new TypedValue(); - final Theme baseTheme = mContext.getTheme(); - baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); - - final Context actionBarContext; - if (outValue.resourceId != 0) { - final Theme actionBarTheme = mContext.getResources().newTheme(); - actionBarTheme.setTo(baseTheme); - actionBarTheme.applyStyle(outValue.resourceId, true); - - actionBarContext = new ContextThemeWrapper(mContext, 0); - actionBarContext.getTheme().setTo(actionBarTheme); - } else { - actionBarContext = mContext; - } - - mPrimaryActionModeView = new ActionBarContextView(actionBarContext); - mPrimaryActionModePopup = new PopupWindow(actionBarContext, null, - R.attr.actionModePopupWindowStyle); - mPrimaryActionModePopup.setWindowLayoutType( - WindowManager.LayoutParams.TYPE_APPLICATION); - mPrimaryActionModePopup.setContentView(mPrimaryActionModeView); - mPrimaryActionModePopup.setWidth(MATCH_PARENT); - - actionBarContext.getTheme().resolveAttribute( - R.attr.actionBarSize, outValue, true); - final int height = TypedValue.complexToDimensionPixelSize(outValue.data, - actionBarContext.getResources().getDisplayMetrics()); - mPrimaryActionModeView.setContentHeight(height); - mPrimaryActionModePopup.setHeight(WRAP_CONTENT); - mShowPrimaryActionModePopup = new Runnable() { - public void run() { - mPrimaryActionModePopup.showAtLocation( - mPrimaryActionModeView.getApplicationWindowToken(), - Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); - endOnGoingFadeAnimation(); - mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, - 0f, 1f); - mFadeAnim.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - mPrimaryActionModeView.setVisibility(VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - mPrimaryActionModeView.setAlpha(1f); - mFadeAnim = null; - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - mFadeAnim.start(); - } - }; - } else { - ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub); - if (stub != null) { - mPrimaryActionModeView = (ActionBarContextView) stub.inflate(); - } - } - } - if (mPrimaryActionModeView != null) { - mPrimaryActionModeView.killMode(); - ActionMode mode = new StandaloneActionMode( - mPrimaryActionModeView.getContext(), mPrimaryActionModeView, - callback, mPrimaryActionModePopup == null); - return mode; - } - return null; - } - - private void endOnGoingFadeAnimation() { - if (mFadeAnim != null) { - mFadeAnim.end(); - } - } - - private void setHandledPrimaryActionMode(ActionMode mode) { - endOnGoingFadeAnimation(); - mPrimaryActionMode = mode; - mPrimaryActionMode.invalidate(); - mPrimaryActionModeView.initForMode(mPrimaryActionMode); - if (mPrimaryActionModePopup != null) { - post(mShowPrimaryActionModePopup); - } else { - mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f); - mFadeAnim.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - mPrimaryActionModeView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - mPrimaryActionModeView.setAlpha(1f); - mFadeAnim = null; - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - mFadeAnim.start(); - } - mPrimaryActionModeView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - private ActionMode createFloatingActionMode( - View originatingView, ActionMode.Callback2 callback) { - if (mFloatingActionMode != null) { - mFloatingActionMode.finish(); - } - cleanupFloatingActionModeViews(); - final FloatingActionMode mode = - new FloatingActionMode(mContext, callback, originatingView); - mFloatingActionModeOriginatingView = originatingView; - mFloatingToolbarPreDrawListener = - new OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mode.updateViewLocationInWindow(); - return true; - } - }; - return mode; - } - - private void setHandledFloatingActionMode(ActionMode mode) { - mFloatingActionMode = mode; - mFloatingToolbar = new FloatingToolbar(mContext, mWindow); - ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar); - mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. - mFloatingActionModeOriginatingView.getViewTreeObserver() - .addOnPreDrawListener(mFloatingToolbarPreDrawListener); - } - - /** - * Informs the decor if a non client decor is attached and visible. - * @param attachedAndVisible true when the decor is visible. - * Note that this will even be called if there is no non client decor. - **/ - void enableNonClientDecor(boolean attachedAndVisible) { - if (mHasNonClientDecor != attachedAndVisible) { - mHasNonClientDecor = attachedAndVisible; - if (getForeground() != null) { - drawableChanged(); - } - } - } - - /** - * Returns true if the window has a non client decor. - * @return If there is a non client decor - even if it is not visible. - **/ - private boolean windowHasNonClientDecor() { - return mHasNonClientDecor; - } - - /** - * Returns true if the Window is free floating and has a shadow (although at some times - * it might not be displaying it, e.g. during a resize). Note that non overlapping windows - * do not have a shadow since it could not be seen anyways (a small screen / tablet - * "tiles" the windows side by side but does not overlap them). - * @return Returns true when the window has a shadow created by the non client decor. - **/ - private boolean windowHasShadow() { - return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId); - } - - void setWindow(PhoneWindow phoneWindow) { - mWindow = phoneWindow; - Context context = getContext(); - if (context instanceof DecorContext) { - DecorContext decorContex = (DecorContext) context; - decorContex.setPhoneWindow(mWindow); - } - } - - /** - * Clears out internal references when the action mode is destroyed. - */ - private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { - private final ActionMode.Callback mWrapped; - - public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { - mWrapped = wrapped; - } - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return mWrapped.onCreateActionMode(mode, menu); - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - requestFitSystemWindows(); - return mWrapped.onPrepareActionMode(mode, menu); - } - - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return mWrapped.onActionItemClicked(mode, item); - } - - public void onDestroyActionMode(ActionMode mode) { - mWrapped.onDestroyActionMode(mode); - final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion - >= Build.VERSION_CODES.M; - final boolean isPrimary; - final boolean isFloating; - if (isMncApp) { - isPrimary = mode == mPrimaryActionMode; - isFloating = mode == mFloatingActionMode; - if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) { - Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; " - + mode + " was not the current primary action mode! Expected " - + mPrimaryActionMode); - } - if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) { - Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; " - + mode + " was not the current floating action mode! Expected " - + mFloatingActionMode); - } - } else { - isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY; - isFloating = mode.getType() == ActionMode.TYPE_FLOATING; - } - if (isPrimary) { - if (mPrimaryActionModePopup != null) { - removeCallbacks(mShowPrimaryActionModePopup); - } - if (mPrimaryActionModeView != null) { - endOnGoingFadeAnimation(); - mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, - 1f, 0f); - mFadeAnim.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - mPrimaryActionModeView.setVisibility(GONE); - if (mPrimaryActionModePopup != null) { - mPrimaryActionModePopup.dismiss(); - } - mPrimaryActionModeView.removeAllViews(); - mFadeAnim = null; - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - mFadeAnim.start(); - } - - mPrimaryActionMode = null; - } else if (isFloating) { - cleanupFloatingActionModeViews(); - mFloatingActionMode = null; - } - if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { - try { - mWindow.getCallback().onActionModeFinished(mode); - } catch (AbstractMethodError ame) { - // Older apps might not implement this callback method. - } - } - requestFitSystemWindows(); - } - - @Override - public void onGetContentRect(ActionMode mode, View view, Rect outRect) { - if (mWrapped instanceof ActionMode.Callback2) { - ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); - } else { - super.onGetContentRect(mode, view, outRect); - } - } - } - } - protected DecorView generateDecor(int featureId) { // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the @@ -4147,7 +2593,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } else { decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } - mContentRoot = (ViewGroup) in; + decor.mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { @@ -4450,7 +2896,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * isn't in our features, this throws an exception). * @return The panel state. */ - private PanelFeatureState getPanelState(int featureId, boolean required) { + PanelFeatureState getPanelState(int featureId, boolean required) { return getPanelState(featureId, required, null); } @@ -4914,7 +3360,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { int curAlpha = 255; } - private static final class PanelFeatureState { + static final class PanelFeatureState { /** Feature ID for this panel. */ int featureId; @@ -5316,30 +3762,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - private static class ColorViewState { - View view = null; - int targetVisibility = View.INVISIBLE; - boolean present = false; - - final int id; - final int systemUiHideFlag; - final int translucentFlag; - final int verticalGravity; - final int horizontalGravity; - final String transitionName; - final int hideWindowFlag; + int getLocalFeaturesPrivate() { + return super.getLocalFeatures(); + } - ColorViewState(int systemUiHideFlag, - int translucentFlag, int verticalGravity, int horizontalGravity, - String transitionName, int id, int hideWindowFlag) { - this.id = id; - this.systemUiHideFlag = systemUiHideFlag; - this.translucentFlag = translucentFlag; - this.verticalGravity = verticalGravity; - this.horizontalGravity = horizontalGravity; - this.transitionName = transitionName; - this.hideWindowFlag = hideWindowFlag; - } + protected void setDefaultWindowFormat(int format) { + super.setDefaultWindowFormat(format); } void sendCloseSystemWindows() { diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 703a9bd2fcd5..a805b6d1d151 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -1006,6 +1006,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { // is disposed. int dupFd = dup(blob.fd()); if (dupFd < 0) { + ALOGE("Error allocating dup fd. Error:%d", errno); blob.release(); SkSafeUnref(ctable); doThrowRE(env, "Could not allocate dup blob fd."); diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp index c89f293c05a2..2d23cda5ff15 100644 --- a/core/jni/android_util_Log.cpp +++ b/core/jni/android_util_Log.cpp @@ -42,9 +42,7 @@ struct levels_t { static levels_t levels; static jboolean isLoggable(const char* tag, jint level) { - return __android_log_is_loggable(level, tag, - ANDROID_LOG_INFO | - ANDROID_LOGGABLE_FLAG_NOT_WITHIN_SIGNAL); + return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO); } static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level) diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index f900301c61d5..6f6d1b0b43df 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -420,7 +420,7 @@ <string name="permdesc_useFingerprint" msgid="9165097460730684114">"指紋ハードウェアを認証に使用することをアプリに許可します"</string> <string name="fingerprint_acquired_partial" msgid="735082772341716043">"指紋を一部しか検出できませんでした。もう一度お試しください。"</string> <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"指紋を処理できませんでした。もう一度お試しください。"</string> - <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string> + <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋認証センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string> <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"指の動きが速すぎました。もう一度お試しください。"</string> <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"指の動きが遅すぎました。もう一度お試しください。"</string> <string-array name="fingerprint_acquired_vendor"> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 50cf30262f69..90fc22b8b3b8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3242,6 +3242,14 @@ i </p> --> <attr name="canRequestFilterKeyEvents" format="boolean" /> + <!-- Attribute whether the accessibility service wants to be able to control + display magnification. + <p> + Required to allow setting the {@link android.accessibilityservice + #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag. + </p> + --> + <attr name="canControlMagnification" format="boolean" /> <!-- Short description of the accessibility serivce purpose or behavior.--> <attr name="description" /> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 54e43c8e3e7c..b6b2e204b9cf 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2682,6 +2682,7 @@ <public type="attr" name="forceDeviceEncrypted" /> <public type="attr" name="encryptionAware" /> <public type="attr" name="preferenceFragmentStyle" /> + <public type="attr" name="canControlMagnification" /> <public type="style" name="Theme.Material.DayNight" /> <public type="style" name="Theme.Material.DayNight.DarkActionBar" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index faa76f2fe9ce..00c0fe8b26e1 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -613,6 +613,12 @@ <string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit card numbers and passwords.</string> + <!-- Title for the capability of an accessibility service to control display magnification. --> + <string name="capability_title_canControlMagnification">Control display magnification</string> + <!-- Description for the capability of an accessibility service to control display magnification. --> + <string name="capability_desc_canControlMagnification">Control the display\'s zoom level and + positioning.</string> + <!-- Permissions --> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6820c259a51a..e8f6b462d447 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -564,6 +564,8 @@ <java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" /> <java-symbol type="string" name="capability_title_canRequestTouchExploration" /> <java-symbol type="string" name="capability_title_canRetrieveWindowContent" /> + <java-symbol type="string" name="capability_desc_canControlMagnification" /> + <java-symbol type="string" name="capability_title_canControlMagnification" /> <java-symbol type="string" name="cfTemplateForwarded" /> <java-symbol type="string" name="cfTemplateForwardedTime" /> <java-symbol type="string" name="cfTemplateNotForwarded" /> diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd index e22dc4a4bda2..21e3057aaf04 100644 --- a/docs/html/guide/topics/manifest/uses-feature-element.jd +++ b/docs/html/guide/topics/manifest/uses-feature-element.jd @@ -563,8 +563,8 @@ is sensitive to delays or lag in sound input or output.</td> <td rowspan="6">Camera</td> <td><code>android.hardware.camera</code></td> <td>The application uses the device's back-facing (main) camera.</td> - <td>Importantly, devices with only a front-facing camera will not list this - feature, so the <code>android.hardware.camera.any</code> feature should be + <td>Devices with only a front-facing camera do not list this feature, so the + <code>android.hardware.camera.any</code> feature should be used instead if a camera facing any direction is acceptable for the application.</td> </tr> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 64f269886c70..a50c945ca4f6 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1240,30 +1240,54 @@ public abstract class Drawable { */ public static abstract class ConstantState { /** - * Create a new drawable without supplying resources the caller - * is running in. Note that using this means the density-dependent - * drawables (like bitmaps) will not be able to update their target - * density correctly. One should use {@link #newDrawable(Resources)} - * instead to provide a resource. + * Creates a new Drawable instance from its constant state. + * <p> + * <strong>Note:</strong> Using this method means density-dependent + * properties, such as pixel dimensions or bitmap images, will not be + * updated to match the density of the target display. To ensure + * correct scaling, use {@link #newDrawable(Resources)} instead to + * provide an appropriate Resources object. + * + * @return a new drawable object based on this constant state + * @see {@link #newDrawable(Resources)} */ + @NonNull public abstract Drawable newDrawable(); /** - * Create a new Drawable instance from its constant state. This - * must be implemented for drawables that change based on the target - * density of their caller (that is depending on whether it is - * in compatibility mode). + * Creates a new Drawable instance from its constant state using the + * specified resources. This method should be implemented for drawables + * that have density-dependent properties. + * <p> + * The default implementation for this method calls through to + * {@link #newDrawable()}. + * + * @param res the resources of the context in which the drawable will + * be displayed + * @return a new drawable object based on this constant state */ - public Drawable newDrawable(Resources res) { + @NonNull + public Drawable newDrawable(@Nullable Resources res) { return newDrawable(); } /** - * Create a new Drawable instance from its constant state. This must be - * implemented for drawables that can have a theme applied. + * Creates a new Drawable instance from its constant state using the + * specified resources and theme. This method should be implemented for + * drawables that have theme-dependent properties. + * <p> + * The default implementation for this method calls through to + * {@link #newDrawable(Resources)}. + * + * @param res the resources of the context in which the drawable will + * be displayed + * @param theme the theme of the context in which the drawable will be + * displayed + * @return a new drawable object based on this constant state */ - public Drawable newDrawable(Resources res, Theme theme) { - return newDrawable(null); + @NonNull + public Drawable newDrawable(@Nullable Resources res, @Nullable Theme theme) { + return newDrawable(res); } /** diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 4acad67f716a..8565372f3b53 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -206,7 +206,9 @@ LOCAL_MODULE := hwui_unit_tests LOCAL_MODULE_TAGS := tests LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu -LOCAL_CFLAGS := $(hwui_cflags) +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU LOCAL_SRC_FILES += \ unit_tests/CanvasStateTests.cpp \ diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 1aa291f23a1e..d2d3285b71f8 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -35,11 +35,11 @@ OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t h LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height); - startRepaintLayer(buffer); + startRepaintLayer(buffer, Rect(width, height)); return buffer; } -void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) { +void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) { LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); mRenderTarget.offscreenBuffer = offscreenBuffer; @@ -55,12 +55,10 @@ void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) { LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, "framebuffer incomplete!"); - // Clear the FBO - mRenderState.scissor().setEnabled(false); - glClear(GL_COLOR_BUFFER_BIT); - // Change the viewport & ortho projection setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight); + + clearColorBuffer(repaintRect); } void BakedOpRenderer::endLayer() { @@ -74,16 +72,13 @@ void BakedOpRenderer::endLayer() { mRenderTarget.frameBufferId = -1; } -void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) { +void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) { mRenderState.bindFramebuffer(0); setViewport(width, height); mCaches.clearGarbage(); if (!mOpaque) { - // TODO: partial invalidate! - mRenderState.scissor().setEnabled(false); - glClear(GL_COLOR_BUFFER_BIT); - mHasDrawn = true; + clearColorBuffer(repaintRect); } } @@ -113,6 +108,20 @@ void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) { mRenderState.blend().syncEnabled(); } +void BakedOpRenderer::clearColorBuffer(const Rect& rect) { + if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) { + // Full viewport is being cleared - disable scissor + mRenderState.scissor().setEnabled(false); + } else { + // Requested rect is subset of viewport - scissor to it to avoid over-clearing + mRenderState.scissor().setEnabled(true); + mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom, + rect.getWidth(), rect.getHeight()); + } + glClear(GL_COLOR_BUFFER_BIT); + if (!mRenderTarget.frameBufferId) mHasDrawn = true; +} + Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); if (!texture) { @@ -136,7 +145,7 @@ void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) { mRenderTarget.offscreenBuffer->region.orSelf(dirty); } mRenderState.render(glop, mRenderTarget.orthoMatrix); - mHasDrawn = true; + if (!mRenderTarget.frameBufferId) mHasDrawn = true; } //////////////////////////////////////////////////////////////////////////////// @@ -217,7 +226,7 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f paint.setAntiAlias(true); // want to use AlphaVertex // The caller has made sure casterAlpha > 0. - uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha; + uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha; if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { ambientShadowAlpha = Properties::overrideAmbientShadowStrength; } @@ -227,7 +236,7 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f paint, VertexBufferRenderFlags::ShadowInterp); } - uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha; + uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha; if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { spotShadowAlpha = Properties::overrideSpotShadowStrength; } @@ -240,12 +249,10 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) { TessellationCache::vertexBuffer_pair_t buffers; - Vector3 lightCenter = { 300, 300, 300 }; // TODO! - float lightRadius = 150; // TODO! - renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform, op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath, - &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius, + &op.shadowMatrixXY, &op.shadowMatrixZ, + op.lightCenter, renderer.getLightInfo().lightRadius, buffers); renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second); diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index d6d9cb139328..29f9a6f81242 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -39,27 +39,39 @@ class RenderState; */ class BakedOpRenderer { public: - BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque) + /** + * Position agnostic shadow lighting info. Used with all shadow ops in scene. + */ + struct LightInfo { + float lightRadius = 0; + uint8_t ambientShadowAlpha = 0; + uint8_t spotShadowAlpha = 0; + }; + + BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo) : mRenderState(renderState) , mCaches(caches) - , mOpaque(opaque) { + , mOpaque(opaque) + , mLightInfo(lightInfo) { } RenderState& renderState() { return mRenderState; } Caches& caches() { return mCaches; } - void startFrame(uint32_t width, uint32_t height); + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect); void endFrame(); OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height); - void startRepaintLayer(OffscreenBuffer* offscreenBuffer); + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect); void endLayer(); Texture* getTexture(const SkBitmap* bitmap); + const LightInfo& getLightInfo() { return mLightInfo; } void renderGlop(const BakedOpState& state, const Glop& glop); bool didDraw() { return mHasDrawn; } private: void setViewport(uint32_t width, uint32_t height); + void clearColorBuffer(const Rect& clearRect); RenderState& mRenderState; Caches& mCaches; @@ -75,6 +87,8 @@ private: uint32_t viewportHeight = 0; Matrix4 orthoMatrix; } mRenderTarget; + + const LightInfo mLightInfo; }; /** diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index a327614a50ef..94a11f131229 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -79,7 +79,6 @@ bool Caches::init() { } void Caches::initExtensions() { - mExtensions.load(); if (mExtensions.hasDebugMarker()) { eventMark = glInsertEventMarkerEXT; diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 03b1706faa49..39b7ecb9a914 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -40,7 +40,6 @@ void DeviceInfo::initialize() { } void DeviceInfo::load() { - mExtensions.load(); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); } diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index e257715acaeb..02caaa49e99c 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -35,7 +35,7 @@ namespace uirenderer { #endif -void Extensions::load() { +Extensions::Extensions() { auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS)); mHasNPot = extensions.has("GL_OES_texture_npot"); mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch"); diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 8ccfabdd2450..67cc747015e0 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -31,7 +31,7 @@ namespace uirenderer { class Extensions { public: - void load(); + Extensions(); inline bool hasNPot() const { return mHasNPot; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index 80efaed44d13..b04f16fe4788 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -18,6 +18,7 @@ #include "LayerUpdateQueue.h" #include "RenderNode.h" +#include "renderstate/OffscreenBufferPool.h" #include "utils/FatVector.h" #include "utils/PaintUtils.h" @@ -33,8 +34,8 @@ class BatchBase { public: BatchBase(batchid_t batchId, BakedOpState* op, bool merging) - : mBatchId(batchId) - , mMerging(merging) { + : mBatchId(batchId) + , mMerging(merging) { mBounds = op->computedState.clippedBounds; mOps.push_back(op); } @@ -207,9 +208,10 @@ private: }; OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, - const BeginLayerOp* beginLayerOp, RenderNode* renderNode) + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) : width(width) , height(height) + , repaintRect(repaintRect) , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) , beginLayerOp(beginLayerOp) , renderNode(renderNode) {} @@ -309,15 +311,19 @@ void OpReorderer::LayerReorderer::dump() const { OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, - const std::vector< sp<RenderNode> >& nodes) + const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter) : mCanvasState(*this) { ATRACE_NAME("prepare drawing commands"); - mLayerReorderers.emplace_back(viewportWidth, viewportHeight); - mLayerStack.push_back(0); + mLayerReorderers.reserve(layers.entries().size()); + mLayerStack.reserve(layers.entries().size()); + + // Prepare to defer Fbo0 + mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip)); + mLayerStack.push_back(0); mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, - Vector3()); + lightCenter); // 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) @@ -325,7 +331,8 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, RenderNode* layerNode = layers.entries()[i].renderNode; const Rect& layerDamage = layers.entries()[i].damage; - saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode); + saveForLayer(layerNode->getWidth(), layerNode->getHeight(), + layerDamage, nullptr, layerNode); mCanvasState.writableSnapshot()->setClip( layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom); @@ -345,14 +352,17 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, } } -OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList) +OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList, + const Vector3& lightCenter) : mCanvasState(*this) { ATRACE_NAME("prepare drawing commands"); - mLayerReorderers.emplace_back(viewportWidth, viewportHeight); + // Prepare to defer Fbo0 + mLayerReorderers.emplace_back(viewportWidth, viewportHeight, + Rect(viewportWidth, viewportHeight)); mLayerStack.push_back(0); - mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, - 0, 0, viewportWidth, viewportHeight, Vector3()); + 0, 0, viewportWidth, viewportHeight, lightCenter); + deferImpl(displayList); } @@ -508,7 +518,8 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) { } ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath, - mCanvasState.getLocalClipBounds()); + mCanvasState.getLocalClipBounds(), + mCanvasState.currentSnapshot()->getRelativeLightCenter()); BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct( mAllocator, *mCanvasState.currentSnapshot(), shadowOp); if (CC_LIKELY(bakedOpState)) { @@ -589,17 +600,36 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); } -void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, +void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) { + auto previous = mCanvasState.currentSnapshot(); mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); mCanvasState.writableSnapshot()->transform->loadIdentity(); mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); mCanvasState.writableSnapshot()->roundRectClipState = nullptr; + Vector3 lightCenter = previous->getRelativeLightCenter(); + if (renderNode) { + Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow; + inverse.mapPoint3d(lightCenter); + } else { + // Combine all transforms used to present saveLayer content: + // parent content transform * canvas transform * bounds offset + Matrix4 contentTransform(*previous->transform); + contentTransform.multiply(beginLayerOp->localMatrix); + contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top); + + // inverse the total transform, to map light center into layer-relative space + Matrix4 inverse; + inverse.loadInverse(contentTransform); + inverse.mapPoint3d(lightCenter); + } + mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter); + // create a new layer, and push its index on the stack mLayerStack.push_back(mLayerReorderers.size()); - mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode); + mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode); } void OpReorderer::restoreForLayer() { @@ -612,7 +642,7 @@ void OpReorderer::restoreForLayer() { 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); + saveForLayer(layerWidth, layerHeight, Rect(layerWidth, layerHeight), &op, nullptr); } void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 2c30f0dc30de..09d5cbcf7559 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -67,13 +67,13 @@ class OpReorderer : public CanvasStateClient { class LayerReorderer { public: // Create LayerReorderer for Fbo0 - LayerReorderer(uint32_t width, uint32_t height) - : LayerReorderer(width, height, nullptr, nullptr) {}; + LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect) + : LayerReorderer(width, height, repaintRect, 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); + const Rect& repaintRect, 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 @@ -101,6 +101,7 @@ class OpReorderer : public CanvasStateClient { const uint32_t width; const uint32_t height; + const Rect repaintRect; OffscreenBuffer* offscreenBuffer; const BeginLayerOp* beginLayerOp; const RenderNode* renderNode; @@ -116,14 +117,15 @@ class OpReorderer : public CanvasStateClient { // Maps batch ids to the most recent *non-merging* batch of that id OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr }; - }; + public: OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, - const std::vector< sp<RenderNode> >& nodes); + const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter); - OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList); + OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList, + const Vector3& lightCenter); virtual ~OpReorderer() {} @@ -153,7 +155,7 @@ public: LayerReorderer& layer = mLayerReorderers[i]; if (layer.renderNode) { // cached HW layer - can't skip layer if empty - renderer.startRepaintLayer(layer.offscreenBuffer); + renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect); layer.replayBakedOpsImpl((void*)&renderer, receivers); renderer.endLayer(); } else if (!layer.empty()) { // save layer - skip entire layer if empty @@ -164,7 +166,7 @@ public: } const LayerReorderer& fbo0 = mLayerReorderers[0]; - renderer.startFrame(fbo0.width, fbo0.height); + renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect); fbo0.replayBakedOpsImpl((void*)&renderer, receivers); renderer.endFrame(); } @@ -188,7 +190,7 @@ private: Positive }; void saveForLayer(uint32_t layerWidth, uint32_t layerHeight, - const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode); void restoreForLayer(); LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; } diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index bb7a0a7ce446..ef0536761a11 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -17,10 +17,11 @@ #ifndef ANDROID_HWUI_RECORDED_OP_H #define ANDROID_HWUI_RECORDED_OP_H -#include "utils/LinearAllocator.h" -#include "Rect.h" #include "Matrix.h" +#include "Rect.h" #include "RenderNode.h" +#include "utils/LinearAllocator.h" +#include "Vector.h" #include "SkXfermode.h" @@ -116,15 +117,17 @@ struct RectOp : RecordedOp { * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time, * and are resolved dynamically, and transform isn't needed. * - * State construction handles these properties specially. + * State construction handles these properties specially, ignoring matrix/bounds. */ struct ShadowOp : RecordedOp { - ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect) + ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, + const Rect& clipRect, const Vector3& lightCenter) : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr) , shadowMatrixXY(casterOp.localMatrix) , shadowMatrixZ(casterOp.localMatrix) , casterAlpha(casterAlpha) - , casterPath(casterPath) { + , casterPath(casterPath) + , lightCenter(lightCenter) { const RenderNode& node = *casterOp.renderNode; node.applyViewPropertyTransforms(shadowMatrixXY, false); node.applyViewPropertyTransforms(shadowMatrixZ, true); @@ -133,6 +136,7 @@ struct ShadowOp : RecordedOp { Matrix4 shadowMatrixZ; const float casterAlpha; const SkPath* casterPath; + const Vector3 lightCenter; }; struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index e9885552369d..6ab253c2491f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -156,7 +156,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight()); - snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f); + snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f); Rect clip = layerBounds; clip.translate(-untransformedBounds.left, -untransformedBounds.top); diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index e177f9a86a2c..2713f46ab33b 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -326,15 +326,11 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { return; } - if (transformUpdateNeeded) { + if (transformUpdateNeeded && mLayer) { // 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 diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 0a58f4b42e4c..2f535bb1cb08 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -130,6 +130,9 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) { /////////////////////////////////////////////////////////////////////////////// void Snapshot::resetTransform(float x, float y, float z) { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("not supported - light center managed differently"); +#else // before resetting, map current light pos with inverse of current transform Vector3 center = mRelativeLightCenter; mat4 inverse; @@ -139,6 +142,7 @@ void Snapshot::resetTransform(float x, float y, float z) { transform = &mTransformRoot; transform->loadTranslate(x, y, z); +#endif } void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp index 7b8d0e542a2f..b24858e73525 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -48,7 +48,7 @@ BENCHMARK_NO_ARG(BM_OpReorderer_defer); void BM_OpReorderer_defer::Run(int iters) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(200, 200, *sReorderingDisplayList); + OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 }); MicroBench::DoNotOptimize(&reorderer); } StopBenchmarkTiming(); @@ -59,11 +59,13 @@ void BM_OpReorderer_deferAndRender::Run(int iters) { TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) { RenderState& renderState = thread.renderState(); Caches& caches = Caches::getInstance(); + BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 }; + StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(200, 200, *sReorderingDisplayList); + OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 }); - BakedOpRenderer renderer(caches, renderState, true); + BakedOpRenderer renderer(caches, renderState, true, lightInfo); reorderer.replayBakedOps<BakedOpDispatcher>(renderer); MicroBench::DoNotOptimize(&renderer); } diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h index f0fd82d0444f..fac6c35179d5 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.h +++ b/libs/hwui/renderstate/OffscreenBufferPool.h @@ -34,6 +34,11 @@ class RenderState; * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and * encompasses enough information to draw it back on screen (minus paint properties, which are held * by LayerOp). + * + * Has two distinct sizes - viewportWidth/viewportHeight describe content area, + * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the + * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for + * the purpose of improving reuse. */ class OffscreenBuffer { public: @@ -44,11 +49,17 @@ public: // must be called prior to rendering, to construct/update vertex buffer void updateMeshFromRegion(); + // Set by RenderNode for HW layers, TODO for clipped saveLayers + void setWindowTransform(const Matrix4& transform) { + inverseTransformInWindow.loadInverse(transform); + } + static uint32_t computeIdealDimension(uint32_t dimension); uint32_t getSizeInBytes() { return texture.width * texture.height * 4; } RenderState& renderState; + uint32_t viewportWidth; uint32_t viewportHeight; Texture texture; @@ -56,6 +67,10 @@ public: // Portion of layer that has been drawn to. Used to minimize drawing area when // drawing back to screen / parent FBO. Region region; + + Matrix4 inverseTransformInWindow; + + // vbo / size of mesh GLsizei elementCount = 0; GLuint vbo = 0; }; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f094b2d0c289..89cadea775f3 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -31,7 +31,6 @@ #include "utils/TimeUtils.h" #if HWUI_NEW_OPS -#include "BakedOpRenderer.h" #include "OpReorderer.h" #endif @@ -150,13 +149,23 @@ bool CanvasContext::pauseSurface(ANativeWindow* window) { // TODO: don't pass viewport size, it's automatic via EGL void CanvasContext::setup(int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { +#if HWUI_NEW_OPS + mLightInfo.lightRadius = lightRadius; + mLightInfo.ambientShadowAlpha = ambientShadowAlpha; + mLightInfo.spotShadowAlpha = spotShadowAlpha; +#else if (!mCanvas) return; mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha); +#endif } void CanvasContext::setLightCenter(const Vector3& lightCenter) { +#if HWUI_NEW_OPS + mLightCenter = lightCenter; +#else if (!mCanvas) return; mCanvas->setLightCenter(lightCenter); +#endif } void CanvasContext::setOpaque(bool opaque) { @@ -340,9 +349,11 @@ void CanvasContext::draw() { mEglManager.damageFrame(frame, dirty); #if HWUI_NEW_OPS - OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes); + OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), + mRenderNodes, mLightCenter); mLayerUpdateQueue.clear(); - BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque); + BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), + mOpaque, mLightInfo); // TODO: profiler().draw(mCanvas); reorderer.replayBakedOps<BakedOpDispatcher>(renderer); @@ -576,8 +587,12 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); +#else mCanvas->markLayersAsBuildLayers(); mCanvas->flushLayerUpdates(); +#endif node->incStrong(nullptr); mPrefetechedLayers.insert(node); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index d656014fdbcb..c3cfc940281e 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -27,6 +27,10 @@ #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" +#if HWUI_NEW_OPS +#include "BakedOpRenderer.h" +#endif + #include <cutils/compiler.h> #include <EGL/egl.h> #include <SkBitmap.h> @@ -165,6 +169,11 @@ private: bool mOpaque; OpenGLRenderer* mCanvas = nullptr; +#if HWUI_NEW_OPS + BakedOpRenderer::LightInfo mLightInfo; + Vector3 mLightCenter = { 0, 0, 0 }; +#endif + bool mHaveNewSurface = false; DamageAccumulator mDamageAccumulator; LayerUpdateQueue mLayerUpdateQueue; diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index 07080a2f3730..a8c9bba32e64 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -29,6 +29,14 @@ namespace android { namespace uirenderer { LayerUpdateQueue sEmptyLayerUpdateQueue; +Vector3 sLightCenter = {100, 100, 100}; + +static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) { + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + std::vector<sp<RenderNode>> vec; + vec.emplace_back(node); + return vec; +} /** * Virtual class implemented by each test to redirect static operation / state transitions to @@ -48,13 +56,13 @@ public: ADD_FAILURE() << "Layer creation not expected in this test"; return nullptr; } - virtual void startRepaintLayer(OffscreenBuffer*) { + virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) { 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 startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {} virtual void endFrame() {} // define virtual defaults for direct @@ -87,7 +95,7 @@ class FailRenderer : public TestRendererBase {}; TEST(OpReorderer, simple) { class SimpleTestRenderer : public TestRendererBase { public: - void startFrame(uint32_t width, uint32_t height) override { + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { EXPECT_EQ(0, mIndex++); EXPECT_EQ(100u, width); EXPECT_EQ(200u, height); @@ -108,7 +116,7 @@ TEST(OpReorderer, simple) { canvas.drawRect(0, 0, 100, 200, SkPaint()); canvas.drawBitmap(bitmap, 10, 10, nullptr); }); - OpReorderer reorderer(100, 200, *dl); + OpReorderer reorderer(100, 200, *dl, sLightCenter); SimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -122,7 +130,7 @@ TEST(OpReorderer, simpleRejection) { canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.restore(); }); - OpReorderer reorderer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl, sLightCenter); FailRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -154,7 +162,7 @@ TEST(OpReorderer, simpleBatching) { canvas.restore(); }); - OpReorderer reorderer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl, sLightCenter); SimpleBatchingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -198,13 +206,8 @@ TEST(OpReorderer, renderNode) { canvas.restore(); }); - TestUtils::syncHierarchyPropertiesAndDisplayList(parent); - - std::vector< sp<RenderNode> > nodes; - nodes.push_back(parent.get()); - - OpReorderer reorderer(sEmptyLayerUpdateQueue, - SkRect::MakeWH(200, 200), 200, 200, nodes); + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + createSyncedNodeList(parent), sLightCenter); RenderNodeTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -225,13 +228,10 @@ TEST(OpReorderer, clipped) { SkBitmap bitmap = TestUtils::createSkBitmap(200, 200); canvas.drawBitmap(bitmap, 0, 0, nullptr); }); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - std::vector< sp<RenderNode> > nodes; - nodes.push_back(node.get()); OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver - 200, 200, nodes); + 200, 200, createSyncedNodeList(node), sLightCenter); ClippedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -273,7 +273,7 @@ TEST(OpReorderer, saveLayerSimple) { canvas.restore(); }); - OpReorderer reorderer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl, sLightCenter); SaveLayerSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -305,7 +305,7 @@ TEST(OpReorderer, saveLayerNested) { int index = mIndex++; EXPECT_TRUE(index == 2 || index == 6); } - void startFrame(uint32_t width, uint32_t height) override { + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { EXPECT_EQ(7, mIndex++); } void endFrame() override { @@ -344,7 +344,7 @@ TEST(OpReorderer, saveLayerNested) { canvas.restore(); }); - OpReorderer reorderer(800, 800, *dl); + OpReorderer reorderer(800, 800, *dl, sLightCenter); SaveLayerNestedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -363,19 +363,21 @@ TEST(OpReorderer, saveLayerContentRejection) { canvas.restore(); canvas.restore(); }); - OpReorderer reorderer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl, sLightCenter); FailRenderer renderer; // should see no ops, even within the layer, since the layer should be rejected reorderer.replayBakedOps<TestDispatcher>(renderer); } -TEST(OpReorderer, hwLayerSimple) { +RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) { class HwLayerSimpleTestRenderer : public TestRendererBase { public: - void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override { + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { EXPECT_EQ(0, mIndex++); - EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124); + EXPECT_EQ(100u, offscreenBuffer->viewportWidth); + EXPECT_EQ(100u, offscreenBuffer->viewportHeight); + EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); } void onRectOp(const RectOp& op, const BakedOpState& state) override { EXPECT_EQ(1, mIndex++); @@ -389,7 +391,7 @@ TEST(OpReorderer, hwLayerSimple) { void endLayer() override { EXPECT_EQ(2, mIndex++); } - void startFrame(uint32_t width, uint32_t height) override { + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { EXPECT_EQ(3, mIndex++); } void onLayerOp(const LayerOp& op, const BakedOpState& state) override { @@ -405,29 +407,29 @@ TEST(OpReorderer, hwLayerSimple) { paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); }, TestUtils::getHwLayerSetupCallback()); - OffscreenBuffer** bufferHandle = node->getLayerHandle(); - *bufferHandle = (OffscreenBuffer*) 0x0124; + OffscreenBuffer** layerHandle = node->getLayerHandle(); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); + // create RenderNode's layer here in same way prepareTree would + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); + *layerHandle = &layer; - std::vector< sp<RenderNode> > nodes; - nodes.push_back(node.get()); + auto syncedNodeList = createSyncedNodeList(node); // only enqueue partial damage - LayerUpdateQueue layerUpdateQueue; + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); - OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes); - + OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedNodeList, sLightCenter); HwLayerSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(6, renderer.getIndex()); // clean up layer pointer, so we can safely destruct RenderNode - *bufferHandle = nullptr; + *layerHandle = nullptr; } -TEST(OpReorderer, hwLayerComplex) { +RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) { /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: * - startRepaintLayer(child), rect(grey), endLayer * - startTemporaryLayer, drawLayer(child), endLayer @@ -440,14 +442,16 @@ TEST(OpReorderer, hwLayerComplex) { EXPECT_EQ(3, mIndex++); // savelayer first return (OffscreenBuffer*)0xabcd; } - void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override { + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { int index = mIndex++; if (index == 0) { // starting inner layer - EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer); + EXPECT_EQ(100u, offscreenBuffer->viewportWidth); + EXPECT_EQ(100u, offscreenBuffer->viewportHeight); } else if (index == 6) { // starting outer layer - EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer); + EXPECT_EQ(200u, offscreenBuffer->viewportWidth); + EXPECT_EQ(200u, offscreenBuffer->viewportHeight); } else { ADD_FAILURE(); } } void onRectOp(const RectOp& op, const BakedOpState& state) override { @@ -464,17 +468,20 @@ TEST(OpReorderer, hwLayerComplex) { int index = mIndex++; EXPECT_TRUE(index == 2 || index == 5 || index == 9); } - void startFrame(uint32_t width, uint32_t height) override { + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { EXPECT_EQ(10, mIndex++); } void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + OffscreenBuffer* layer = *op.layerHandle; int index = mIndex++; if (index == 4) { - EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle); + EXPECT_EQ(100u, layer->viewportWidth); + EXPECT_EQ(100u, layer->viewportHeight); } else if (index == 8) { EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); } else if (index == 11) { - EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle); + EXPECT_EQ(200u, layer->viewportWidth); + EXPECT_EQ(200u, layer->viewportHeight); } else { ADD_FAILURE(); } } void endFrame() override { @@ -488,7 +495,8 @@ TEST(OpReorderer, hwLayerComplex) { paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); }, TestUtils::getHwLayerSetupCallback()); - *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567; + OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100); + *(child->getLayerHandle()) = &childLayer; RenderNode* childPtr = child.get(); auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, @@ -501,18 +509,17 @@ TEST(OpReorderer, hwLayerComplex) { canvas.drawRenderNode(childPtr); canvas.restore(); }, TestUtils::getHwLayerSetupCallback()); - *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123; - - TestUtils::syncHierarchyPropertiesAndDisplayList(parent); + OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); + *(parent->getLayerHandle()) = &parentLayer; - std::vector< sp<RenderNode> > nodes; - nodes.push_back(parent.get()); + auto syncedList = createSyncedNodeList(parent); - LayerUpdateQueue layerUpdateQueue; + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); - OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes); + OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedList, sLightCenter); HwLayerComplexTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -561,58 +568,184 @@ TEST(OpReorderer, zReorder) { drawOrderedRect(&canvas, 8); drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder }); - TestUtils::syncHierarchyPropertiesAndDisplayList(parent); - - std::vector< sp<RenderNode> > nodes; - nodes.push_back(parent.get()); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes); + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + createSyncedNodeList(parent), sLightCenter); ZReorderTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(10, renderer.getIndex()); }; +// creates a 100x100 shadow casting node with provided translationZ +static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { + return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, + [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }, [translationZ] (RenderProperties& properties) { + properties.setTranslationZ(translationZ); + properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); + return RenderNode::GENERIC | RenderNode::TRANSLATION_Z; + }); +} + TEST(OpReorderer, shadow) { class ShadowTestRenderer : public TestRendererBase { public: void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { EXPECT_EQ(0, mIndex++); + EXPECT_FLOAT_EQ(1.0f, op.casterAlpha); + EXPECT_TRUE(op.casterPath->isRect(nullptr)); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY); + + Matrix4 expectedZ; + expectedZ.loadTranslate(0, 0, 5); + EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ); } void onRectOp(const RectOp& op, const BakedOpState& state) override { EXPECT_EQ(1, mIndex++); } }; - sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, - [](RecordingCanvas& canvas) { - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - }, [] (RenderProperties& properties) { - properties.setTranslationZ(5.0f); - properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f); - return RenderNode::GENERIC | RenderNode::TRANSLATION_Z; + sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + [] (RecordingCanvas& canvas) { + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); }); + + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + createSyncedNodeList(parent), sLightCenter); + + ShadowTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +TEST(OpReorderer, shadowSaveLayer) { + class ShadowSaveLayerTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + return nullptr; + } + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + EXPECT_FLOAT_EQ(50, op.lightCenter.x); + EXPECT_FLOAT_EQ(40, op.lightCenter.y); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void endLayer() override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + }; + sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, - [&caster] (RecordingCanvas& canvas) { + [] (RecordingCanvas& canvas) { + // save/restore outside of reorderBarrier, so they don't get moved out of place + canvas.translate(20, 10); + int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag); canvas.insertReorderBarrier(true); - canvas.drawRenderNode(caster.get()); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.insertReorderBarrier(false); + canvas.restoreToCount(count); }); - TestUtils::syncHierarchyPropertiesAndDisplayList(parent); + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + createSyncedNodeList(parent), (Vector3) { 100, 100, 100 }); - std::vector< sp<RenderNode> > nodes; - nodes.push_back(parent.get()); + ShadowSaveLayerTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(5, renderer.getIndex()); +} - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes); +RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { + class ShadowHwLayerTestRenderer : public TestRendererBase { + public: + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + } + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + EXPECT_FLOAT_EQ(50, op.lightCenter.x); + EXPECT_FLOAT_EQ(40, op.lightCenter.y); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void endLayer() override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + }; - ShadowTestRenderer renderer; + sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160, + [] (RecordingCanvas& canvas) { + canvas.insertReorderBarrier(true); + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(20, 10); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.restore(); + }, TestUtils::getHwLayerSetupCallback()); + OffscreenBuffer** layerHandle = parent->getLayerHandle(); + + // create RenderNode's layer here in same way prepareTree would, setting windowTransform + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); + Matrix4 windowTransform; + windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin + layer.setWindowTransform(windowTransform); + *layerHandle = &layer; + + auto syncedList = createSyncedNodeList(parent); + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); + OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedList, (Vector3) { 100, 100, 100 }); + + ShadowHwLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); + EXPECT_EQ(5, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; } -static void testProperty( - TestUtils::PropSetupCallback propSetupCallback, +TEST(OpReorderer, shadowLayering) { + class ShadowLayeringTestRenderer : public TestRendererBase { + public: + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 0 || index == 1); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 3); + } + }; + sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + [] (RecordingCanvas& canvas) { + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get()); + }); + + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + createSyncedNodeList(parent), sLightCenter); + + ShadowLayeringTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + + +static void testProperty(TestUtils::PropSetupCallback propSetupCallback, std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { class PropertyTestRenderer : public TestRendererBase { public: @@ -630,13 +763,9 @@ static void testProperty( paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); }, propSetupCallback); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - - std::vector< sp<RenderNode> > nodes; - nodes.push_back(node.get()); - OpReorderer reorderer(sEmptyLayerUpdateQueue, - SkRect::MakeWH(100, 100), 200, 200, nodes); + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, + createSyncedNodeList(node), sLightCenter); PropertyTestRenderer renderer(opValidateCallback); reorderer.replayBakedOps<TestDispatcher>(renderer); diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h index efa28ae90262..38bafd52cac8 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/unit_tests/TestUtils.h @@ -45,6 +45,20 @@ namespace uirenderer { && MathUtils::areEqual(a.right, b.right) \ && MathUtils::areEqual(a.bottom, b.bottom)); +/** + * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope + * (for e.g. accessing its RenderState) + */ +#define RENDERTHREAD_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + TEST(test_case_name, test_name) { \ + TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \ + }; \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread) + class TestUtils { public: class SignalingDtor { diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java index 043b80e7d117..6197c707c752 100644 --- a/media/java/android/media/tv/TvContentRating.java +++ b/media/java/android/media/tv/TvContentRating.java @@ -931,9 +931,7 @@ public final class TvContentRating { * * @param rating The {@link TvContentRating} to check. * @return {@code true} if this object contains {@code rating}, {@code false} otherwise. - * @hide */ - @SystemApi public final boolean contains(@NonNull TvContentRating rating) { Preconditions.checkNotNull(rating); if (!rating.getMainRating().equals(mRating)) { diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java index 6f1a89baf525..369ab7dc59ea 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java @@ -32,6 +32,8 @@ import android.provider.DocumentsContract.Document; import android.test.MoreAsserts; import android.test.ServiceTestCase; import android.test.mock.MockContentResolver; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.android.documentsui.model.DocumentInfo; @@ -52,6 +54,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +@MediumTest public class CopyTest extends ServiceTestCase<CopyService> { public CopyTest() { @@ -89,9 +92,6 @@ public class CopyTest extends ServiceTestCase<CopyService> { super.tearDown(); } - /** - * Test copying a single file. - */ public void testCopyFile() throws Exception { String srcPath = "/test0.txt"; Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", @@ -131,9 +131,6 @@ public class CopyTest extends ServiceTestCase<CopyService> { MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent); } - /** - * Test copying multiple files. - */ public void testCopyMultipleFiles() throws Exception { String testContent[] = { "The five boxing wizards jump quickly", diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java index 906051640c6a..ba91c83b95e8 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java @@ -31,11 +31,13 @@ import android.support.test.uiautomator.Configurator; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.view.MotionEvent; import com.android.documentsui.model.RootInfo; +@LargeTest public class FilesActivityUiTest extends InstrumentationTestCase { private static final int TIMEOUT = 5000; diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java index 746e2117dc02..b250e5d344c4 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java @@ -25,6 +25,7 @@ import android.provider.DocumentsContract.Document; import android.support.v7.widget.RecyclerView; import android.test.AndroidTestCase; import android.test.mock.MockContentResolver; +import android.test.suitebuilder.annotation.SmallTest; import android.view.ViewGroup; import com.android.documentsui.DirectoryResult; @@ -34,6 +35,7 @@ import com.android.documentsui.model.DocumentInfo; import java.util.List; +@SmallTest public class DirectoryFragmentModelTest extends AndroidTestCase { private static final int ITEM_COUNT = 5; diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java index d1ce56457a87..b3d45aee48b1 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java @@ -18,6 +18,7 @@ package com.android.documentsui.dirlist; import android.support.v7.widget.RecyclerView; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseBooleanArray; import android.view.View; import android.view.ViewGroup; @@ -32,6 +33,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +@SmallTest public class MultiSelectManagerTest extends AndroidTestCase { private static final List<String> items; @@ -163,7 +165,6 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertRangeSelection(14, 17); } - public void testSingleTapUp_ShiftReversesSelectionDirection() { longPress(7); shiftTap(17); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java index c4b6ce5c7f87..c856b2290ea1 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java @@ -20,11 +20,13 @@ import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseBooleanArray; import android.view.View; import com.android.documentsui.dirlist.MultiSelectManager.GridModel; +@SmallTest public class MultiSelectManager_GridModelTest extends AndroidTestCase { private static final int VIEW_PADDING_PX = 5; diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java index 64da750b1306..72fc10814e72 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java @@ -17,10 +17,11 @@ package com.android.documentsui.dirlist; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; import com.android.documentsui.dirlist.MultiSelectManager.Selection; - +@SmallTest public class MultiSelectManager_SelectionTest extends AndroidTestCase{ private Selection selection; diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java index 0d4265a6417c..1c96906a1d79 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java @@ -18,7 +18,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.database.Cursor; -import android.database.MatrixCursor; +import android.database.sqlite.SQLiteException; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.Bundle; @@ -26,6 +26,7 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Date; @@ -44,12 +45,14 @@ class DocumentLoader { private final MtpManager mMtpManager; private final ContentResolver mResolver; + private final MtpDatabase mDatabase; private final TaskList mTaskList = new TaskList(); private boolean mHasBackgroundThread = false; - DocumentLoader(MtpManager mtpManager, ContentResolver resolver) { + DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { mMtpManager = mtpManager; mResolver = resolver; + mDatabase = database; } private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles) @@ -65,13 +68,17 @@ class DocumentLoader { throws IOException { LoaderTask task = mTaskList.findTask(parent); if (task == null) { + if (parent.mDocumentId == null) { + throw new FileNotFoundException("Parent not found."); + } + int parentHandle = parent.mObjectHandle; // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to // getObjectHandles if we would like to obtain children under the root. if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; } - task = new LoaderTask(parent, mMtpManager.getObjectHandles( + task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); task.fillDocuments(loadDocuments( mMtpManager, @@ -83,11 +90,10 @@ class DocumentLoader { } mTaskList.addFirst(task); - if (!task.completed() && !mHasBackgroundThread) { + if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) { mHasBackgroundThread = true; new BackgroundLoaderThread().start(); } - return task.createCursor(mResolver, columnNames); } @@ -120,26 +126,20 @@ class DocumentLoader { deviceId = task.mIdentifier.mDeviceId; handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES); } - MtpObjectInfo[] objectInfos; + try { - objectInfos = loadDocuments(mMtpManager, deviceId, handles); - } catch (IOException exception) { - objectInfos = null; - Log.d(MtpDocumentsProvider.TAG, exception.getMessage()); - } - synchronized (DocumentLoader.this) { - if (objectInfos != null) { - task.fillDocuments(objectInfos); - final boolean shouldNotify = - task.mLastNotified.getTime() < - new Date().getTime() - NOTIFY_PERIOD_MS || - task.completed(); - if (shouldNotify) { - task.notify(mResolver); - } - } else { - mTaskList.remove(task); + final MtpObjectInfo[] objectInfos = + loadDocuments(mMtpManager, deviceId, handles); + task.fillDocuments(objectInfos); + final boolean shouldNotify = + task.mLastNotified.getTime() < + new Date().getTime() - NOTIFY_PERIOD_MS || + task.getState() != LoaderTask.STATE_LOADING; + if (shouldNotify) { + task.notify(mResolver); } + } catch (IOException exception) { + task.setError(exception); } } } @@ -156,7 +156,7 @@ class DocumentLoader { LoaderTask findRunningTask() { for (int i = 0; i < size(); i++) { - if (!get(i).completed()) + if (get(i).getState() == LoaderTask.STATE_LOADING) return get(i); } return null; @@ -165,7 +165,7 @@ class DocumentLoader { void clearCompletedTasks() { int i = 0; while (i < size()) { - if (get(i).completed()) { + if (get(i).getState() == LoaderTask.STATE_COMPLETED) { remove(i); } else { i++; @@ -186,36 +186,51 @@ class DocumentLoader { } private static class LoaderTask { + static final int STATE_LOADING = 0; + static final int STATE_COMPLETED = 1; + static final int STATE_ERROR = 2; + + final MtpDatabase mDatabase; final Identifier mIdentifier; final int[] mObjectHandles; - final MtpObjectInfo[] mObjectInfos; Date mLastNotified; int mNumLoaded; + Exception mError; - LoaderTask(Identifier identifier, int[] objectHandles) { + LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) { + mDatabase = database; mIdentifier = identifier; mObjectHandles = objectHandles; - mObjectInfos = new MtpObjectInfo[mObjectHandles.length]; mNumLoaded = 0; mLastNotified = new Date(); } - Cursor createCursor(ContentResolver resolver, String[] columnNames) { - final MatrixCursor cursor = new MatrixCursor(columnNames); - final Identifier rootIdentifier = new Identifier( - mIdentifier.mDeviceId, mIdentifier.mStorageId); - for (int i = 0; i < mNumLoaded; i++) { - CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow()); - } + Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException { final Bundle extras = new Bundle(); - extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed()); + switch (getState()) { + case STATE_LOADING: + extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); + break; + case STATE_ERROR: + throw new IOException(mError); + } + + final Cursor cursor = mDatabase.queryChildDocuments( + columnNames, mIdentifier.mDocumentId, /* use old ID format */ true); cursor.setNotificationUri(resolver, createUri()); cursor.respond(extras); + return cursor; } - boolean completed() { - return mNumLoaded == mObjectInfos.length; + int getState() { + if (mError != null) { + return STATE_ERROR; + } else if (mNumLoaded == mObjectHandles.length) { + return STATE_COMPLETED; + } else { + return STATE_LOADING; + } } int[] getUnloadedObjectHandles(int count) { @@ -230,9 +245,32 @@ class DocumentLoader { mLastNotified = new Date(); } - void fillDocuments(MtpObjectInfo[] objectInfos) { - for (int i = 0; i < objectInfos.length; i++) { - mObjectInfos[mNumLoaded++] = objectInfos[i]; + void fillDocuments(MtpObjectInfo[] objectInfoList) { + if (objectInfoList.length == 0 || getState() != STATE_LOADING) { + return; + } + if (mNumLoaded == 0) { + mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId); + } + try { + mDatabase.putChildDocuments( + mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList); + mNumLoaded += objectInfoList.length; + } catch (SQLiteException exp) { + mError = exp; + mNumLoaded = 0; + } + if (getState() != STATE_LOADING) { + mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId); + } + } + + void setError(Exception message) { + final int lastState = getState(); + mError = message; + mNumLoaded = 0; + if (lastState == STATE_LOADING) { + mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId); } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java index ae29f526e3ec..4238721e1238 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java @@ -23,6 +23,7 @@ class Identifier { final int mDeviceId; final int mStorageId; final int mObjectHandle; + final String mDocumentId; static Identifier createFromRootId(String rootId) { final String[] components = rootId.split("_"); @@ -45,9 +46,14 @@ class Identifier { } Identifier(int deviceId, int storageId, int objectHandle) { + this(deviceId, storageId, objectHandle, null); + } + + Identifier(int deviceId, int storageId, int objectHandle, String documentId) { mDeviceId = deviceId; mStorageId = storageId; mObjectHandle = objectHandle; + mDocumentId = documentId; } // TODO: Make the ID persistent. diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 3151ccb3c9cd..3dc69ccbf1ee 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.mtp.MtpObjectInfo; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; @@ -79,8 +80,8 @@ class MtpDatabase { private final Map<String, Integer> mMappingMode = new HashMap<>(); @VisibleForTesting - MtpDatabase(Context context) { - mDatabase = new MtpDatabaseInternal(context); + MtpDatabase(Context context, int flags) { + mDatabase = new MtpDatabaseInternal(context, flags); } /** @@ -111,7 +112,29 @@ class MtpDatabase { */ @VisibleForTesting Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) { - return mDatabase.queryChildDocuments(columnNames, parentDocumentId); + return queryChildDocuments(columnNames, parentDocumentId, false); + } + + @VisibleForTesting + Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) { + final String[] newColumnNames = new String[columnNames.length]; + + // TODO: Temporary replace document ID with old format. + for (int i = 0; i < columnNames.length; i++) { + if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) { + newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID + + " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " + + DocumentsContract.Document.COLUMN_DOCUMENT_ID; + } else { + newColumnNames[i] = columnNames[i]; + } + } + + return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId); + } + + Identifier createIdentifier(String parentDocumentId) { + return mDatabase.createIdentifier(parentDocumentId); } /** @@ -193,9 +216,13 @@ class MtpDatabase { int i = 0; for (final MtpRoot root : roots) { // Use the same value for the root ID and the corresponding document ID. - values.put( - Root.COLUMN_ROOT_ID, - valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID)); + final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID); + // If it fails to insert/update documents, the document ID will be set with -1. + // In this case we don't insert/update root extra information neither. + if (documentId == null) { + continue; + } + values.put(Root.COLUMN_ROOT_ID, documentId); values.put( Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java index 977b12ed7366..97c1d293172c 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java @@ -26,6 +26,9 @@ class MtpDatabaseConstants { static final int DATABASE_VERSION = 1; static final String DATABASE_NAME = null; + static final int FLAG_DATABASE_IN_MEMORY = 1; + static final int FLAG_DATABASE_IN_FILE = 0; + /** * Table representing documents including root documents. */ diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java index 7328f054b16d..9c5d6b6ad3ec 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java @@ -23,6 +23,7 @@ import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.provider.DocumentsContract.Document; @@ -35,8 +36,11 @@ import java.util.Objects; */ class MtpDatabaseInternal { private static class OpenHelper extends SQLiteOpenHelper { - public OpenHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); + public OpenHelper(Context context, int flags) { + super(context, + flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME, + null, + DATABASE_VERSION); } @Override @@ -54,8 +58,8 @@ class MtpDatabaseInternal { private final SQLiteDatabase mDatabase; - MtpDatabaseInternal(Context context) { - final OpenHelper helper = new OpenHelper(context); + MtpDatabaseInternal(Context context, int flags) { + final OpenHelper helper = new OpenHelper(context, flags); mDatabase = helper.getWritableDatabase(); } @@ -122,6 +126,64 @@ class MtpDatabaseInternal { } /** + * Gets identifier from document ID. + * @param documentId Document ID. + * @return Identifier. + */ + Identifier createIdentifier(String documentId) { + // Currently documentId is old format. + final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId); + final String selection; + final String[] args; + if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) { + selection = COLUMN_DEVICE_ID + "= ? AND " + + COLUMN_ROW_STATE + " IN (?, ?) AND " + + COLUMN_STORAGE_ID + "= ? AND " + + COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; + args = strings( + oldIdentifier.mDeviceId, + ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + oldIdentifier.mStorageId); + } else { + selection = COLUMN_DEVICE_ID + "= ? AND " + + COLUMN_ROW_STATE + " IN (?, ?) AND " + + COLUMN_STORAGE_ID + "= ? AND " + + COLUMN_OBJECT_HANDLE + " = ?"; + args = strings( + oldIdentifier.mDeviceId, + ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + oldIdentifier.mStorageId, + oldIdentifier.mObjectHandle); + } + + final Cursor cursor = mDatabase.query( + TABLE_DOCUMENTS, + strings(Document.COLUMN_DOCUMENT_ID), + selection, + args, + null, + null, + null, + "1"); + try { + if (cursor.getCount() == 0) { + return oldIdentifier; + } else { + cursor.moveToNext(); + return new Identifier( + oldIdentifier.mDeviceId, + oldIdentifier.mStorageId, + oldIdentifier.mObjectHandle, + cursor.getString(0)); + } + } finally { + cursor.close(); + } + } + + /** * Starts adding new documents. * The methods decides mapping mode depends on if all documents under the given parent have MTP * identifier or not. If all the documents have MTP identifier, it uses the identifier to find @@ -164,7 +226,7 @@ class MtpDatabaseInternal { * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * - * @param valuesList Values that are stored in the database. + * @param valuesList Values for documents to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param arg Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. @@ -191,23 +253,32 @@ class MtpDatabaseInternal { null, null, "1"); - final long rowId; - if (candidateCursor.getCount() == 0) { - rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); - added = true; - } else if (!heuristic) { - candidateCursor.moveToNext(); - final String documentId = candidateCursor.getString(0); - rowId = mDatabase.update( - TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId)); - } else { - values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING); - rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); + try { + final long rowId; + if (candidateCursor.getCount() == 0) { + rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); + if (rowId == -1) { + throw new SQLiteException("Failed to put a document into database."); + } + added = true; + } else if (!heuristic) { + candidateCursor.moveToNext(); + final String documentId = candidateCursor.getString(0); + rowId = mDatabase.update( + TABLE_DOCUMENTS, + values, + SELECTION_DOCUMENT_ID, + strings(documentId)); + } else { + values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING); + rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); + } + // Document ID is a primary integer key of the table. So the returned row + // IDs should be same with the document ID. + values.put(Document.COLUMN_DOCUMENT_ID, rowId); + } finally { + candidateCursor.close(); } - // Document ID is a primary integer key of the table. So the returned row - // IDs should be same with the document ID. - values.put(Document.COLUMN_DOCUMENT_ID, rowId); - candidateCursor.close(); } mDatabase.setTransactionSuccessful(); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 0931445b1035..f0f816173586 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -78,7 +78,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); - mDatabase = new MtpDatabase(getContext()); + mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); return true; } @@ -155,7 +155,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (projection == null) { projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; } - final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId); + final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); try { return getDocumentLoader(parentIdentifier).queryChildDocuments( projection, parentIdentifier); @@ -255,7 +255,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { mMtpManager.openDevice(deviceId); - mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver)); + mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); mRootScanner.scanNow(); } @@ -318,9 +318,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; - public DeviceToolkit(MtpManager manager, ContentResolver resolver) { + public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { mPipeManager = new PipeManager(); - mDocumentLoader = new DocumentLoader(manager, resolver); + mDocumentLoader = new DocumentLoader(manager, resolver, database); } } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index 415f89e4acab..d9ed4ab5dc4a 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -2,6 +2,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.res.Resources; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.Process; import android.provider.DocumentsContract; @@ -93,16 +94,17 @@ final class RootScanner { } boolean changed = false; for (int deviceId : deviceIds) { + mDatabase.startAddingRootDocuments(deviceId); try { - mDatabase.startAddingRootDocuments(deviceId); changed = mDatabase.putRootDocuments( deviceId, mResources, mManager.getRoots(deviceId)) || changed; - changed = mDatabase.stopAddingRootDocuments(deviceId) || changed; - } catch (IOException exp) { + } catch (IOException|SQLiteException exp) { // The error may happen on the device. We would like to continue getting // roots for other devices. Log.e(MtpDocumentsProvider.TAG, exp.getMessage()); continue; + } finally { + changed = mDatabase.stopAddingRootDocuments(deviceId) || changed; } } if (changed) { diff --git a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml index 28ad3f4e53c0..e1307e90ac64 100644 --- a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml +++ b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml @@ -18,7 +18,4 @@ <instrumentation android:name="com.android.mtp.TestResultInstrumentation" android:targetPackage="com.android.mtp" android:label="Tests for MtpDocumentsProvider with the UI for output." /> - <instrumentation android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.mtp" - android:label="Tests for MtpDocumentsProvider." /> </manifest> diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java index a012d7fd49bc..a80eb51b43a0 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java @@ -22,26 +22,37 @@ import android.mtp.MtpObjectInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.MediumTest; import java.io.IOException; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; -@SmallTest +@MediumTest public class DocumentLoaderTest extends AndroidTestCase { + private MtpDatabase mDatabase; private BlockableTestMtpManager mManager; private TestContentResolver mResolver; private DocumentLoader mLoader; - final private Identifier mParentIdentifier = new Identifier(0, 0, 0); + final private Identifier mParentIdentifier = new Identifier(0, 0, 0, "1"); @Override public void setUp() { + mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] { + new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") + }); + mDatabase.stopAddingRootDocuments(0); mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); - mLoader = new DocumentLoader(mManager, mResolver); + mLoader = new DocumentLoader(mManager, mResolver, mDatabase); + } + + @Override + public void tearDown() { + mDatabase.close(); } public void testBasic() throws Exception { @@ -88,6 +99,7 @@ public class DocumentLoaderTest extends AndroidTestCase { childDocuments[i] = objectHandle; manager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(objectHandle) + .setName(Integer.toString(i)) .build()); } manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index ce4cf14d822c..25dd1c83766e 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -41,18 +41,29 @@ public class MtpDatabaseTest extends AndroidTestCase { }; private final TestResources resources = new TestResources(); + MtpDatabase mDatabase; + + @Override + public void setUp() { + mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); + } + + @Override + public void tearDown() { + mDatabase.close(); + mDatabase = null; + } public void testPutRootDocuments() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""), new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""), new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"") }); { - final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES); + final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES); assertEquals(3, cursor.getCount()); cursor.moveToNext(); @@ -80,7 +91,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(new String [] { + final Cursor cursor = mDatabase.queryRoots(new String [] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, @@ -136,15 +147,14 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testPutChildDocuments() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); - database.startAddingChildDocuments("parentId"); - database.putChildDocuments(0, "parentId", new MtpObjectInfo[] { + mDatabase.startAddingChildDocuments("parentId"); + mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] { createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024), createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024) }); - final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId"); + final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId"); assertEquals(3, cursor.getCount()); cursor.moveToNext(); @@ -202,7 +212,6 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testRestoreIdForRootDocuments() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, @@ -212,14 +221,15 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_ROOT_ID, Root.COLUMN_AVAILABLE_BYTES }; - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""), new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "") }); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -233,7 +243,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -244,10 +254,10 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.clearMapping(); + mDatabase.clearMapping(); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -261,7 +271,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -272,14 +282,14 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""), new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "") }); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(3, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -297,7 +307,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(3, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -311,10 +321,10 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(0); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -328,7 +338,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -341,22 +351,21 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testRestoreIdForChildDocuments() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_OBJECT_HANDLE, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - database.startAddingChildDocuments("parentId"); - database.putChildDocuments(0, "parentId", new MtpObjectInfo[] { + mDatabase.startAddingChildDocuments("parentId"); + mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] { createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024), createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024) }); - database.clearMapping(); + mDatabase.clearMapping(); { - final Cursor cursor = database.queryChildDocuments(columns, "parentId"); + final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId"); assertEquals(3, cursor.getCount()); cursor.moveToNext(); @@ -377,14 +386,14 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.startAddingChildDocuments("parentId"); - database.putChildDocuments(0, "parentId", new MtpObjectInfo[] { + mDatabase.startAddingChildDocuments("parentId"); + mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024), }); { - final Cursor cursor = database.queryChildDocuments(columns, "parentId"); + final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId"); assertEquals(4, cursor.getCount()); cursor.moveToPosition(3); @@ -395,10 +404,10 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.stopAddingChildDocuments("parentId"); + mDatabase.stopAddingChildDocuments("parentId"); { - final Cursor cursor = database.queryChildDocuments(columns, "parentId"); + final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId"); assertEquals(2, cursor.getCount()); cursor.moveToNext(); @@ -415,7 +424,6 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testRestoreIdForDifferentDevices() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, @@ -425,17 +433,17 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_ROOT_ID, Root.COLUMN_AVAILABLE_BYTES }; - database.startAddingRootDocuments(0); - database.startAddingRootDocuments(1); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.startAddingRootDocuments(1); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, "") }); - database.putRootDocuments(1, resources, new MtpRoot[] { + mDatabase.putRootDocuments(1, resources, new MtpRoot[] { new MtpRoot(1, 100, "Device", "Storage", 0, 0, "") }); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -449,7 +457,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -460,21 +468,21 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - database.clearMapping(); + mDatabase.clearMapping(); - database.startAddingRootDocuments(0); - database.startAddingRootDocuments(1); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.startAddingRootDocuments(1); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "") }); - database.putRootDocuments(1, resources, new MtpRoot[] { + mDatabase.putRootDocuments(1, resources, new MtpRoot[] { new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "") }); - database.stopAddingRootDocuments(0); - database.stopAddingRootDocuments(1); + mDatabase.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(1); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -488,7 +496,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -501,34 +509,33 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testRestoreIdForDifferentParents() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_OBJECT_HANDLE }; - database.startAddingChildDocuments("parentId1"); - database.startAddingChildDocuments("parentId2"); - database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] { + mDatabase.startAddingChildDocuments("parentId1"); + mDatabase.startAddingChildDocuments("parentId2"); + mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] { createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); - database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] { + mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] { createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); - database.clearMapping(); + mDatabase.clearMapping(); - database.startAddingChildDocuments("parentId1"); - database.startAddingChildDocuments("parentId2"); - database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] { + mDatabase.startAddingChildDocuments("parentId1"); + mDatabase.startAddingChildDocuments("parentId2"); + mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); - database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] { + mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] { createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); - database.stopAddingChildDocuments("parentId1"); + mDatabase.stopAddingChildDocuments("parentId1"); { - final Cursor cursor = database.queryChildDocuments(columns, "parentId1"); + final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -536,7 +543,7 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } { - final Cursor cursor = database.queryChildDocuments(columns, "parentId2"); + final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 2, cursor.getInt(0)); @@ -546,7 +553,6 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testClearMtpIdentifierBeforeResolveRootDocuments() { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, @@ -557,26 +563,26 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES }; - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); - database.clearMapping(); + mDatabase.clearMapping(); - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), }); - database.clearMapping(); + mDatabase.clearMapping(); - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""), }); - database.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(0); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -585,7 +591,7 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -595,7 +601,6 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testPutSameNameRootsAfterClearing() throws Exception { - final MtpDatabase database = new MtpDatabase(getContext()); final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, @@ -606,21 +611,21 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES }; - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); - database.clearMapping(); + mDatabase.clearMapping(); - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""), }); - database.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(0); { - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 2, cursor.getInt(0)); @@ -633,7 +638,7 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } { - final Cursor cursor = database.queryRoots(rootColumns); + final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 2, cursor.getInt(0)); @@ -646,27 +651,26 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testReplaceExistingRoots() { - // The client code should be able to replace exisitng rows with new information. - final MtpDatabase database = new MtpDatabase(getContext()); + // The client code should be able to replace existing rows with new information. // Add one. - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); - database.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(0); // Replace it. - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), }); - database.stopAddingRootDocuments(0); + mDatabase.stopAddingRootDocuments(0); { final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - final Cursor cursor = database.queryRootDocuments(columns); + final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("documentId", 1, cursor.getInt(0)); @@ -679,7 +683,7 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_ROOT_ID, Root.COLUMN_AVAILABLE_BYTES }; - final Cursor cursor = database.queryRoots(columns); + final Cursor cursor = mDatabase.queryRoots(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("rootId", 1, cursor.getInt(0)); @@ -690,20 +694,19 @@ public class MtpDatabaseTest extends AndroidTestCase { public void _testFailToReplaceExisitingUnmappedRoots() { // The client code should not be able to replace rows before resolving 'unmapped' rows. - final MtpDatabase database = new MtpDatabase(getContext()); // Add one. - database.startAddingRootDocuments(0); - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); - database.clearMapping(); + mDatabase.clearMapping(); // Add one. - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. try { - database.putRootDocuments(0, resources, new MtpRoot[] { + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), }); fail(); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index bc7f28c59f33..82e08cd26927 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -43,7 +43,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mResolver = new TestContentResolver(); mMtpManager = new TestMtpManager(getContext()); mProvider = new MtpDocumentsProvider(); - mDatabase = new MtpDatabase(getContext()); + mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase); } @@ -200,6 +200,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(2) + .setStorageId(1) .setFormat(MtpConstants.FORMAT_EXIF_JPEG) .setName("image.jpg") .setDateModified(1422716400000L) @@ -210,6 +211,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { assertEquals(1, cursor.getCount()); cursor.moveToNext(); + assertEquals("0_1_2", cursor.getString(0)); assertEquals("image/jpeg", cursor.getString(1)); assertEquals("image.jpg", cursor.getString(2)); @@ -227,6 +229,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(2) + .setStorageId(1) .setFormat(MtpConstants.FORMAT_ASSOCIATION) .setName("directory") .setDateModified(1422716400000L) @@ -277,6 +280,12 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider.openDevice(0); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); + mDatabase.startAddingRootDocuments(0); + mDatabase.putRootDocuments(0, mResources, new MtpRoot[] { + new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") + }); + mDatabase.stopAddingRootDocuments(0); + mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(1) .setFormat(MtpConstants.FORMAT_EXIF_JPEG) diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java index 53018cc8d810..7c947f5be147 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java @@ -19,15 +19,14 @@ package com.android.mtp; import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.MediumTest; import java.io.IOException; -import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -@SmallTest +@MediumTest public class PipeManagerTest extends AndroidTestCase { private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' }; diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index 3d92cc22c146..3833799bacc4 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -96,7 +96,7 @@ public class TestMtpManager extends MtpManager { if (mRoots.containsKey(deviceId)) { return mRoots.get(deviceId); } else { - throw new IOException("getRoots error"); + throw new IOException("getRoots error: " + Integer.toString(deviceId)); } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java index a24337534e5b..0fb0f34eec65 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java @@ -12,8 +12,20 @@ public class TestResultInstrumentation extends InstrumentationTestRunner impleme @Override public void onCreate(Bundle arguments) { + if (arguments == null) { + arguments = new Bundle(); + } + final boolean includeRealDeviceTest = + Boolean.parseBoolean(arguments.getString("realDeviceTest", "false")); + if (!includeRealDeviceTest) { + arguments.putString("notAnnotation", "com.android.mtp.RealDeviceTest"); + } super.onCreate(arguments); - addTestListener(this); + if (includeRealDeviceTest) { + // Show the test result by using activity because we need to disconnect USB cable + // from adb host while testing with real MTP device. + addTestListener(this); + } } @Override diff --git a/packages/PrintSpooler/res/drawable/ic_add.xml b/packages/PrintSpooler/res/drawable/ic_add.xml new file mode 100644 index 000000000000..1442b1b61f46 --- /dev/null +++ b/packages/PrintSpooler/res/drawable/ic_add.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" + android:fillColor="#FFFFFF"/> +</vector>
\ No newline at end of file diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml deleted file mode 100644 index 991fa38b2da4..000000000000 --- a/packages/PrintSpooler/res/drawable/ic_search.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android" - android:autoMirrored="true"> - - <item - android:state_checked="true"> - <bitmap - android:src="@*android:drawable/ic_menu_search" - android:tint="?android:attr/colorControlActivated"> - </bitmap> - </item> - - <item - android:state_pressed="true"> - <bitmap - android:src="@*android:drawable/ic_menu_search" - android:tint="?android:attr/colorControlActivated"> - </bitmap> - </item> - - <item> - <bitmap - android:src="@*android:drawable/ic_menu_search" - android:tint="?android:attr/colorControlNormal"> - </bitmap> - </item> - -</selector> diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml new file mode 100644 index 000000000000..11fef2d21fde --- /dev/null +++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textIsSelectable="false" + android:textColor="?android:attr/textColorPrimary" + android:paddingStart="20dip" + android:paddingEnd="8dip" + android:minHeight="56dip" + android:orientation="horizontal" + android:text="@string/destination_default_text" + android:gravity="start|center_vertical" /> diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml index 8da57694007b..15cc13939cf0 100644 --- a/packages/PrintSpooler/res/menu/select_printer_activity.xml +++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml @@ -19,7 +19,7 @@ <item android:id="@+id/action_search" android:title="@string/search" - android:icon="@*android:drawable/ic_search" + android:icon="@*android:drawable/ic_search_api_material" android:actionViewClass="android.widget.SearchView" android:showAsAction="ifRoom|collapseActionView" android:alphabeticShortcut="f" @@ -29,7 +29,7 @@ <item android:id="@+id/action_add_printer" android:title="@string/print_add_printer" - android:icon="@*android:drawable/create_contact" + android:icon="@drawable/ic_add" android:showAsAction="ifRoom" android:alphabeticShortcut="a"> </item> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 70abdf4920bb..6d8178844fba 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -49,6 +49,9 @@ <!-- Label of the page selection widget. [CHAR LIMIT=20] --> <string name="label_pages">Pages</string> + <!-- Label of the destination widget. [CHAR LIMIT=20] --> + <string name="destination_default_text">Select a printer</string> + <!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] --> <string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string> diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index f409fd43d085..e7588359478a 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -64,6 +64,7 @@ import android.text.TextWatcher; import android.util.ArrayMap; import android.util.Log; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; @@ -125,6 +126,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static final String FRAGMENT_TAG = "FRAGMENT_TAG"; + private static final String HAS_PRINTED_PREF = "has_printed"; + private static final int ORIENTATION_PORTRAIT = 0; private static final int ORIENTATION_LANDSCAPE = 1; @@ -187,6 +190,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private Spinner mDestinationSpinner; private DestinationAdapter mDestinationSpinnerAdapter; + private boolean mShowDestinationPrompt; private Spinner mMediaSizeSpinner; private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; @@ -1093,6 +1097,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat updateOptionsUi(); addCurrentPrinterToHistory(); + setUserPrinted(); PageRange[] selectedPages = computeSelectedPages(); if (!Arrays.equals(mSelectedPages, selectedPages)) { @@ -1195,6 +1200,29 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // Print button mPrintButton = (ImageView) findViewById(R.id.print_button); mPrintButton.setOnClickListener(clickListener); + + // Special prompt instead of destination spinner for the first time the user printed + if (!hasUserEverPrinted()) { + mShowDestinationPrompt = true; + + mSummaryCopies.setEnabled(false); + mSummaryPaperSize.setEnabled(false); + + mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + mShowDestinationPrompt = false; + mSummaryCopies.setEnabled(true); + mSummaryPaperSize.setEnabled(true); + updateOptionsUi(); + + mDestinationSpinner.setOnTouchListener(null); + mDestinationSpinnerAdapter.notifyDataSetChanged(); + + return false; + } + }); + } } /** @@ -1332,6 +1360,22 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; } + /** + * Disable all options UI elements, beside the {@link #mDestinationSpinner} + */ + private void disableOptionsUi() { + mCopiesEditText.setEnabled(false); + mCopiesEditText.setFocusable(false); + mMediaSizeSpinner.setEnabled(false); + mColorModeSpinner.setEnabled(false); + mDuplexModeSpinner.setEnabled(false); + mOrientationSpinner.setEnabled(false); + mRangeOptionsSpinner.setEnabled(false); + mPageRangeEditText.setEnabled(false); + mPrintButton.setVisibility(View.GONE); + mMoreOptionsButton.setEnabled(false); + } + void updateOptionsUi() { // Always update the summary. updateSummary(); @@ -1346,32 +1390,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat if (mState != STATE_PRINTER_UNAVAILABLE) { mDestinationSpinner.setEnabled(false); } - mCopiesEditText.setEnabled(false); - mCopiesEditText.setFocusable(false); - mMediaSizeSpinner.setEnabled(false); - mColorModeSpinner.setEnabled(false); - mDuplexModeSpinner.setEnabled(false); - mOrientationSpinner.setEnabled(false); - mRangeOptionsSpinner.setEnabled(false); - mPageRangeEditText.setEnabled(false); - mPrintButton.setVisibility(View.GONE); - mMoreOptionsButton.setEnabled(false); + disableOptionsUi(); return; } // If no current printer, or it has no capabilities, or it is not // available, we disable all print options except the destination. if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) { - mCopiesEditText.setEnabled(false); - mCopiesEditText.setFocusable(false); - mMediaSizeSpinner.setEnabled(false); - mColorModeSpinner.setEnabled(false); - mDuplexModeSpinner.setEnabled(false); - mOrientationSpinner.setEnabled(false); - mRangeOptionsSpinner.setEnabled(false); - mPageRangeEditText.setEnabled(false); - mPrintButton.setVisibility(View.GONE); - mMoreOptionsButton.setEnabled(false); + disableOptionsUi(); return; } @@ -1679,6 +1705,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mCopiesEditText.setText(MIN_COPIES_STRING); mCopiesEditText.requestFocus(); } + + if (mShowDestinationPrompt) { + disableOptionsUi(); + } } private void updateSummary() { @@ -1980,6 +2010,32 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } } + + /** + * Check if the user has ever printed a document + * + * @return true iff the user has ever printed a document + */ + private boolean hasUserEverPrinted() { + SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE); + + return preferences.getBoolean(HAS_PRINTED_PREF, false); + } + + /** + * Remember that the user printed a document + */ + private void setUserPrinted() { + SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE); + + if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) { + SharedPreferences.Editor edit = preferences.edit(); + + edit.putBoolean(HAS_PRINTED_PREF, true); + edit.apply(); + } + } + private final class DestinationAdapter extends BaseAdapter implements PrinterRegistry.OnPrintersChangeListener { private final List<PrinterHolder> mPrinterHolders = new ArrayList<>(); @@ -1988,6 +2044,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private boolean mHistoricalPrintersLoaded; + /** + * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt + */ + private boolean hadPromptView; + public DestinationAdapter() { mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded(); if (mHistoricalPrintersLoaded) { @@ -2098,9 +2159,20 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat @Override public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = getLayoutInflater().inflate( - R.layout.printer_dropdown_item, parent, false); + if (mShowDestinationPrompt) { + if (convertView == null) { + convertView = getLayoutInflater().inflate( + R.layout.printer_dropdown_prompt, parent, false); + hadPromptView = true; + } + + return convertView; + } else { + // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it + if (hadPromptView || convertView == null) { + convertView = getLayoutInflater().inflate( + R.layout.printer_dropdown_item, parent, false); + } } CharSequence title = null; diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 9e48849226a3..a37196e48891 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -348,4 +348,7 @@ <!-- Header for items under the work user [CHAR LIMIT=30] --> <string name="category_work">Work</string> + <!-- Full package name of OEM preferred device feedback reporter. Leave this blank, overlaid in Settings/TvSettings [DO NOT TRANSLATE] --> + <string name="oem_preferred_feedback_reporter" translatable="false" /> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java new file mode 100644 index 000000000000..ff1c8665b3d2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -0,0 +1,172 @@ +/* + * 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.settingslib; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DeviceInfoUtils { + private static final String TAG = "DeviceInfoUtils"; + + private static final String FILENAME_PROC_VERSION = "/proc/version"; + private static final String FILENAME_MSV = "/sys/board_properties/soc/msv"; + + /** + * Reads a line from the specified file. + * @param filename the file to read from + * @return the first line, if any. + * @throws IOException if the file couldn't be read + */ + private static String readLine(String filename) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(filename), 256); + try { + return reader.readLine(); + } finally { + reader.close(); + } + } + + public static String getFormattedKernelVersion() { + try { + return formatKernelVersion(readLine(FILENAME_PROC_VERSION)); + } catch (IOException e) { + Log.e(TAG, "IO Exception when getting kernel version for Device Info screen", + e); + + return "Unavailable"; + } + } + + public static String formatKernelVersion(String rawKernelVersion) { + // Example (see tests for more): + // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \ + // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \ + // Thu Jun 28 11:02:39 PDT 2012 + + final String PROC_VERSION_REGEX = + "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */ + "\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */ + "(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */ + "(#\\d+) " + /* group 3: "#1" */ + "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */ + "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */ + + Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion); + if (!m.matches()) { + Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion); + return "Unavailable"; + } else if (m.groupCount() < 4) { + Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount() + + " groups"); + return "Unavailable"; + } + return m.group(1) + "\n" + // 3.0.31-g6fb96c9 + m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1 + m.group(4); // Thu Jun 28 11:02:39 PDT 2012 + } + + /** + * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "". + * @return a string to append to the model number description. + */ + public static String getMsvSuffix() { + // Production devices should have a non-zero value. If we can't read it, assume it's a + // production device so that we don't accidentally show that it's an ENGINEERING device. + try { + String msv = readLine(FILENAME_MSV); + // Parse as a hex number. If it evaluates to a zero, then it's an engineering build. + if (Long.parseLong(msv, 16) == 0) { + return " (ENGINEERING)"; + } + } catch (IOException|NumberFormatException e) { + // Fail quietly, as the file may not exist on some devices, or may be unreadable + } + return ""; + } + + public static String getFeedbackReporterPackage(Context context) { + final String feedbackReporter = + context.getResources().getString(R.string.oem_preferred_feedback_reporter); + if (TextUtils.isEmpty(feedbackReporter)) { + // Reporter not configured. Return. + return feedbackReporter; + } + // Additional checks to ensure the reporter is on system image, and reporter is + // configured to listen to the intent. Otherwise, dont show the "send feedback" option. + final Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> resolvedPackages = + pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER); + for (ResolveInfo info : resolvedPackages) { + if (info.activityInfo != null) { + if (!TextUtils.isEmpty(info.activityInfo.packageName)) { + try { + ApplicationInfo ai = + pm.getApplicationInfo(info.activityInfo.packageName, 0); + if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + // Package is on the system image + if (TextUtils.equals( + info.activityInfo.packageName, feedbackReporter)) { + return feedbackReporter; + } + } + } catch (PackageManager.NameNotFoundException e) { + // No need to do anything here. + } + } + } + } + return null; + } + + public static String getSecurityPatch() { + String patch = Build.VERSION.SECURITY_PATCH; + if (!"".equals(patch)) { + try { + SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd"); + Date patchDate = template.parse(patch); + String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy"); + patch = DateFormat.format(format, patchDate).toString(); + } catch (ParseException e) { + // broken parse; fall through and use the raw string + } + return patch; + } else { + return null; + } + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java new file mode 100644 index 000000000000..8ae8c53a9197 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java @@ -0,0 +1,28 @@ +/* + * 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.systemui.recents; + +import com.android.systemui.recents.events.EventBus; + +/** + * Event sent when the exit animation is started. + * + * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example + * of that is hiding the tasks when the launched application window becomes visible. + */ +public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event { +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 3ae8827b3fc6..1e46e6f54245 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -37,6 +37,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; import android.view.ViewTreeObserver; +import android.view.WindowManager; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; @@ -352,6 +353,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub); mScrimViews = new SystemBarScrimViews(this); + getWindow().getAttributes().privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; // Create the home intent runnable Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -648,6 +651,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); } + public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) { + mRecentsView.setStackViewVisibility(View.INVISIBLE); + mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); + } + public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); int launchToTaskId = launchState.launchedToTaskId; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 50aa2f746f23..b491f943734d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -68,8 +68,8 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; * An implementation of the Recents component for the current user. For secondary users, this can * be called remotely from the system user. */ -public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub - implements ActivityOptions.OnAnimationFinishedListener { +public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements + ActivityOptions.OnAnimationFinishedListener { private final static String TAG = "RecentsImpl"; private final static boolean DEBUG = false; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java index 85b8fcfb4a7f..b2f716d2add5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -27,12 +27,12 @@ import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.Log; -import android.util.SparseArray; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; @@ -106,6 +106,7 @@ public class RecentsTransitionHelper { // If we are launching into another task, cancel the previous task's // window transition EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); if (lockToTask) { // Request screen pinning after the animation runs @@ -116,7 +117,12 @@ public class RecentsTransitionHelper { } else { // This is only the case if the task is not on screen (scrolled offscreen for example) transitionFuture = null; - animStartedListener = null; + animStartedListener = new ActivityOptions.OnAnimationStartedListener() { + @Override + public void onAnimationStarted() { + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); + } + }; } if (taskView == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 3f0000e645d5..069279fdafe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -62,6 +62,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -175,6 +176,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mDeviceInteractive; protected boolean mVisible; + protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; @@ -1664,7 +1666,10 @@ public abstract class BaseStatusBar extends SystemUI implements return; } - final PendingIntent intent = sbn.getNotification().contentIntent; + Notification notification = sbn.getNotification(); + final PendingIntent intent = notification.contentIntent != null + ? notification.contentIntent + : notification.fullScreenIntent; final String notificationKey = sbn.getKey(); // Mark notification for one frame. @@ -1746,8 +1751,8 @@ public abstract class BaseStatusBar extends SystemUI implements } public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - final PendingIntent contentIntent = sbn.getNotification().contentIntent; - if (contentIntent != null) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { row.setOnClickListener(this); } else { row.setOnClickListener(null); @@ -2013,6 +2018,8 @@ public abstract class BaseStatusBar extends SystemUI implements Entry entry = mNotificationData.get(key); if (entry == null) { return; + } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); } Notification n = notification.getNotification(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index bbef1c031ff5..fbe97300aa3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -84,10 +84,10 @@ public class NotificationGroupManager { // the close future. See b/23676310 for reference. return; } - if (notif.isGroupSummary()) { - group.summary = null; - } else { + if (notif.isGroupChild()) { group.children.remove(removed); + } else { + group.summary = null; } if (group.children.isEmpty()) { if (group.summary == null) { @@ -107,17 +107,17 @@ public class NotificationGroupManager { group = new NotificationGroup(); mGroupMap.put(groupKey, group); } - if (notif.isGroupSummary()) { + if (notif.isGroupChild()) { + group.children.add(added); + if (group.summary != null && group.children.size() == 1 && !group.expanded) { + group.summary.row.updateNotificationHeader(); + } + } else { group.summary = added; group.expanded = added.row.areChildrenExpanded(); if (!group.children.isEmpty()) { mListener.onGroupCreatedFromChildren(group); } - } else { - group.children.add(added); - if (group.summary != null && group.children.size() == 1 && !group.expanded) { - group.summary.row.updateNotificationHeader(); - } } } @@ -169,7 +169,7 @@ public class NotificationGroupManager { * @return whether a given notification is a summary in a group which has children */ public boolean isSummaryOfGroup(StatusBarNotification sbn) { - if (sbn.getNotification().isGroupChild()) { + if (!sbn.getNotification().isGroupSummary()) { return false; } NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 3e5251587820..9ab2a2c28793 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -617,7 +617,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap = new HashMap<>(); - private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>(); private RankingMap mLatestRankingMap; private boolean mNoAnimationOnNextBarModeChange; private FalsingManager mFalsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index dc9f5e863379..5cfd17463886 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -458,7 +458,10 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL mReleaseOnExpandFinish = false; } else { for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - removeHeadsUpEntry(entry); + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } } } mEntriesToRemoveAfterExpand.clear(); @@ -596,6 +599,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } if (!hasFullScreenIntent(entry) && !mRemoteInputActive) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index b52687a78298..5f6cbf9f0025 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -21,7 +21,6 @@ import android.os.PowerManager; import android.util.Pools.SimplePool; import android.util.Slog; import android.view.Choreographer; -import android.view.Display; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputFilter; @@ -103,7 +102,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private TouchExplorer mTouchExplorer; - private ScreenMagnifier mScreenMagnifier; + private MagnificationGestureHandler mMagnificationGestureHandler; private AutoclickController mAutoclickController; @@ -363,14 +362,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { - mScreenMagnifier = new ScreenMagnifier(mContext, mUserId, - Display.DEFAULT_DISPLAY, mAms); - addFirstEventHandler(mScreenMagnifier); + mMagnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms); + addFirstEventHandler(mMagnificationGestureHandler); } if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) { - mKeyboardInterceptor = new KeyboardInterceptor(mAms); - addFirstEventHandler(mKeyboardInterceptor); + mKeyboardInterceptor = new KeyboardInterceptor(mAms); + addFirstEventHandler(mKeyboardInterceptor); } } @@ -398,9 +396,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mTouchExplorer.onDestroy(); mTouchExplorer = null; } - if (mScreenMagnifier != null) { - mScreenMagnifier.onDestroy(); - mScreenMagnifier = null; + if (mMagnificationGestureHandler != null) { + mMagnificationGestureHandler.onDestroy(); + mMagnificationGestureHandler = null; } if (mKeyboardInterceptor != null) { mKeyboardInterceptor.onDestroy(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 535a8efc496b..9f1dc0a96559 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -23,6 +23,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.StatusBarManager; @@ -90,6 +91,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBarService; import com.android.server.LocalServices; @@ -181,6 +183,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private MagnificationController mMagnificationController; + private boolean mUnregisterMagnificationOnReset; + private InteractionBridge mInteractionBridge; private AlertDialog mEnableTouchExplorationDialog; @@ -762,6 +766,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** + * Called by the MagnificationController when the state of display + * magnification changes. + * + * @param region the new magnified region, may be empty if + * magnification is not enabled (e.g. scale is 1) + * @param scale the new scale + * @param centerX the new screen-relative center X coordinate + * @param centerY the new screen-relative center Y coordinate + */ + void notifyMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY) { + synchronized (mLock) { + notifyMagnificationChangedLocked(region, scale, centerX, centerY); + + if (mUnregisterMagnificationOnReset && scale == 1.0f) { + mUnregisterMagnificationOnReset = false; + mMagnificationController.unregister(); + } + } + } + + /** * Gets a point within the accessibility focused node where we can send down * and up events to perform a click. * @@ -942,6 +968,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void notifyMagnificationChangedLocked(@NonNull Region region, + float scale, float centerX, float centerY) { + final UserState state = getCurrentUserStateLocked(); + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + service.notifyMagnificationChanged(region, scale, centerX, centerY); + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -1363,6 +1398,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + /** + * Called when any property of the user state has changed. + * + * @param userState the new user state + */ private void onUserStateChangedLocked(UserState userState) { // TODO: Remove this hack mInitialized = true; @@ -1374,6 +1414,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { updateTouchExplorationLocked(userState); updateEnhancedWebAccessibilityLocked(userState); updateDisplayColorAdjustmentSettingsLocked(userState); + updateMagnificationLocked(userState); scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); } @@ -1663,6 +1704,44 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { DisplayAdjustmentUtils.applyAdjustments(mContext, userState.mUserId); } + private void updateMagnificationLocked(UserState userState) { + final int userId = userState.mUserId; + if (userId == mCurrentUserId && mMagnificationController != null) { + if (userHasMagnificationServicesLocked(userState)) { + mMagnificationController.setUserId(userState.mUserId); + } else { + // If the user no longer has any magnification-controlling + // services and is not using magnification gestures, then + // reset the state to normal. + if (!userState.mIsDisplayMagnificationEnabled + && mMagnificationController.resetIfNeeded(true)) { + // Animations are still running, so wait until we receive a + // callback verifying that we've reset magnification. + mUnregisterMagnificationOnReset = true; + } else { + mUnregisterMagnificationOnReset = false; + mMagnificationController.unregister(); + mMagnificationController = null; + } + } + } + } + + /** + * Returns whether the specified user has any services that are capable of + * controlling magnification. + */ + private boolean userHasMagnificationServicesLocked(UserState userState) { + final List<Service> services = userState.mBoundServices; + for (int i = 0, count = services.size(); i < count; i++) { + final Service service = services.get(i); + if (mSecurityPolicy.canControlMagnification(service)) { + return true; + } + } + return false; + } + private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) { IBinder windowToken = mGlobalWindowTokens.get(windowId); if (windowToken == null) { @@ -1938,10 +2017,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } MagnificationController getMagnificationController() { - if (mMagnificationController == null) { - mMagnificationController = new MagnificationController(mContext, this); + synchronized (mLock) { + if (mMagnificationController == null) { + mMagnificationController = new MagnificationController(mContext, this); + mMagnificationController.register(); + mMagnificationController.setUserId(mCurrentUserId); + } + return mMagnificationController; } - return mMagnificationController; } /** @@ -2625,6 +2708,149 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override + public float getMagnificationScale() { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return 1.0f; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + return getMagnificationController().getScale(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public Region getMagnifiedRegion() { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return Region.obtain(); + } + } + final long identity = Binder.clearCallingIdentity(); + try { + final Region region = Region.obtain(); + getMagnificationController().getMagnifiedRegion(region); + return region; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public float getMagnificationCenterX() { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return 0.0f; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + return getMagnificationController().getCenterX(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public float getMagnificationCenterY() { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return 0.0f; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + return getMagnificationController().getCenterY(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public boolean resetMagnification(boolean animate) { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return false; + } + final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this); + if (!permissionGranted) { + return false; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + return getMagnificationController().reset(animate); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, + boolean animate) { + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.USER_CURRENT); + if (resolvedUserId != mCurrentUserId) { + return false; + } + final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this); + if (!permissionGranted) { + return false; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + return getMagnificationController().setScaleAndCenter( + scale, centerX, centerY, animate); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void setMagnificationCallbackEnabled(boolean enabled) { + mInvocationHandler.setMagnificationCallbackEnabled(enabled); + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); synchronized (mLock) { @@ -2819,6 +3045,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE); } + public void notifyMagnificationChanged(@NonNull Region region, + float scale, float centerX, float centerY) { + mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY); + } + + /** + * Called by the invocation handler to notify the service that the + * state of magnification has changed. + */ + private void notifyMagnificationChangedInternal(@NonNull Region region, + float scale, float centerX, float centerY) { + final IAccessibilityServiceClient listener; + synchronized (mLock) { + listener = mServiceInterface; + } + if (listener != null) { + try { + listener.onMagnificationChanged(region, scale, centerX, centerY); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); + } + } + } + private void notifyGestureInternal(int gestureId) { final IAccessibilityServiceClient listener; synchronized (mLock) { @@ -2959,6 +3209,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3; public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4; + private static final int MSG_ON_MAGNIFICATION_CHANGED = 5; + + private boolean mIsMagnificationCallbackEnabled = false; + public InvocationHandler(Looper looper) { super(looper, null, true); } @@ -2987,11 +3241,41 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { setOnKeyEventResult(false, eventState.sequence); } break; + case MSG_ON_MAGNIFICATION_CHANGED: { + final SomeArgs args = (SomeArgs) message.obj; + final Region region = (Region) args.arg1; + final float scale = (float) args.arg2; + final float centerX = (float) args.arg3; + final float centerY = (float) args.arg4; + notifyMagnificationChangedInternal(region, scale, centerX, centerY); + } break; + default: { throw new IllegalArgumentException("Unknown message: " + type); } } } + + public void notifyMagnificationChanged(@NonNull Region region, float scale, + float centerX, float centerY) { + if (!mIsMagnificationCallbackEnabled) { + // Callback is disabled, don't bother packing args. + return; + } + + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = region; + args.arg2 = scale; + args.arg3 = centerX; + args.arg4 = centerY; + + final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args); + msg.sendToTarget(); + } + + public void setMagnificationCallbackEnabled(boolean enabled) { + mIsMagnificationCallbackEnabled = enabled; + } } private final class KeyEventDispatcher { @@ -3660,6 +3944,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0; } + public boolean canControlMagnification(Service service) { + return (service.mAccessibilityServiceInfo.getCapabilities() + & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0; + } + private int resolveProfileParentLocked(int userId) { if (userId != mCurrentUserId) { final long identity = Binder.clearCallingIdentity(); diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index 781d13453be4..a093d9225deb 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -17,20 +17,36 @@ package com.android.server.accessibility; import com.android.internal.R; +import com.android.internal.os.SomeArgs; import com.android.server.LocalServices; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Rect; import android.graphics.Region; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.MathUtils; import android.util.Property; import android.util.Slog; import android.view.MagnificationSpec; +import android.view.View; import android.view.WindowManagerInternal; import android.view.animation.DecelerateInterpolator; +import java.util.Locale; + /** * This class is used to control and query the state of display magnification * from the accessibility manager and related classes. It is responsible for @@ -38,37 +54,71 @@ import android.view.animation.DecelerateInterpolator; * communication between the accessibility manager and window manager. */ class MagnificationController { - private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); + private static final String LOG_TAG = "MagnificationController"; private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; - private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false; - private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec"; + private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; + + private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; + + private static final float MIN_SCALE = 1.0f; + private static final float MAX_SCALE = 5.0f; + + /** + * The minimum scaling factor that can be persisted to secure settings. + * This must be > 1.0 to ensure that magnification is actually set to an + * enabled state when the scaling factor is restored from settings. + */ + private static final float MIN_PERSISTED_SCALE = 2.0f; - private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); + private final Object mLock = new Object(); + + /** + * The current magnification spec. If an animation is running, this + * reflects the end state. + */ private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain(); - private final Region mMagnifiedBounds = new Region(); + private final Region mMagnifiedRegion = Region.obtain(); + private final Region mAvailableRegion = Region.obtain(); + private final Rect mMagnifiedBounds = new Rect(); + private final Rect mTempRect = new Rect(); + private final Rect mTempRect1 = new Rect(); private final AccessibilityManagerService mAms; - private final WindowManagerInternal mWindowManager; - private final ValueAnimator mTransformationAnimator; + private final ContentResolver mContentResolver; + + private final ScreenStateObserver mScreenStateObserver; + private final WindowStateObserver mWindowStateObserver; + + private final SpecAnimationBridge mSpecAnimationBridge; + + private int mUserId; public MagnificationController(Context context, AccessibilityManagerService ams) { mAms = ams; - mWindowManager = LocalServices.getService(WindowManagerInternal.class); + mContentResolver = context.getContentResolver(); + mScreenStateObserver = new ScreenStateObserver(context, this); + mWindowStateObserver = new WindowStateObserver(context, this); + mSpecAnimationBridge = new SpecAnimationBridge(context); + } - final Property<MagnificationController, MagnificationSpec> property = - Property.of(MagnificationController.class, MagnificationSpec.class, - PROPERTY_NAME_MAGNIFICATION_SPEC); - final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator(); - final long animationDuration = context.getResources().getInteger( - R.integer.config_longAnimTime); - mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator, - mSentMagnificationSpec, mCurrentMagnificationSpec); - mTransformationAnimator.setDuration(animationDuration); - mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); + /** + * Registers magnification-related observers. + */ + public void register() { + mScreenStateObserver.register(); + mWindowStateObserver.register(); + } + + /** + * Unregisters magnification-related observers. + */ + public void unregister() { + mScreenStateObserver.unregister(); + mWindowStateObserver.unregister(); } /** @@ -80,26 +130,33 @@ class MagnificationController { } /** - * Sets the magnified region. + * Sets the magnified and available regions. * - * @param region the region to set - * @param updateSpec {@code true} to update the scale and center based on - * the region bounds, {@code false} to leave them as-is + * @param magnified the magnified region + * @param available the region available for magnification + * @param updateSpec {@code true} to update the scale and center based on + * the region bounds, {@code false} to leave them as-is */ - public void setMagnifiedRegion(Region region, boolean updateSpec) { - mMagnifiedBounds.set(region); - - if (updateSpec) { - final Rect magnifiedFrame = mTempRect; - region.getBounds(magnifiedFrame); - final float scale = mSentMagnificationSpec.scale; - final float offsetX = mSentMagnificationSpec.offsetX; - final float offsetY = mSentMagnificationSpec.offsetY; - final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale; - final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale; - setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false); - } else { - mAms.onMagnificationStateChanged(); + public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) { + synchronized (mLock) { + mMagnifiedRegion.set(magnified); + mMagnifiedRegion.getBounds(mMagnifiedBounds); + mAvailableRegion.set(available); + + final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec; + final float scale = sentSpec.scale; + final float offsetX = sentSpec.offsetX; + final float offsetY = sentSpec.offsetY; + + // Compute the new center and update spec as needed. + final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale; + final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale; + if (updateSpec) { + setScaleAndCenter(scale, centerX, centerY, false); + } else { + mAms.onMagnificationStateChanged(); + mAms.notifyMagnificationChanged(mMagnifiedRegion, scale, centerX, centerY); + } } } @@ -113,18 +170,51 @@ class MagnificationController { * magnified region, or {@code false} otherwise */ public boolean magnifiedRegionContains(float x, float y) { - return mMagnifiedBounds.contains((int) x, (int) y); + synchronized (mLock) { + return mMagnifiedRegion.contains((int) x, (int) y); + } } /** - * Populates the specified rect with the bounds of the magnified - * region. + * Returns whether the region available for magnification contains the + * specified screen-relative coordinates. + * + * @param x the screen-relative X coordinate to check + * @param y the screen-relative Y coordinate to check + * @return {@code true} if the coordinate is contained within the + * region available for magnification, or {@code false} otherwise + */ + private boolean availableRegionContains(float x, float y) { + synchronized (mLock) { + return mAvailableRegion.contains((int) x, (int) y); + } + } + + /** + * Populates the specified rect with the screen-relative bounds of the + * magnified region. If magnification is not enabled, the returned + * bounds will be empty. * * @param outBounds rect to populate with the bounds of the magnified * region */ - public void getMagnifiedBounds(Rect outBounds) { - mMagnifiedBounds.getBounds(outBounds); + public void getMagnifiedBounds(@NonNull Rect outBounds) { + synchronized (mLock) { + outBounds.set(mMagnifiedBounds); + } + } + + /** + * Populates the specified region with the screen-relative magnified + * region. If magnification is not enabled, then the returned region + * will be empty. + * + * @param outRegion the region to populate + */ + public void getMagnifiedRegion(@NonNull Region outRegion) { + synchronized (mLock) { + outRegion.set(mMagnifiedRegion); + } } /** @@ -147,6 +237,19 @@ class MagnificationController { return mCurrentMagnificationSpec.offsetX; } + + /** + * Returns the screen-relative X coordinate of the center of the + * magnification viewport. + * + * @return the X coordinate + */ + public float getCenterX() { + synchronized (mLock) { + return (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale(); + } + } + /** * Returns the Y offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. @@ -158,6 +261,18 @@ class MagnificationController { } /** + * Returns the screen-relative Y coordinate of the center of the + * magnification viewport. + * + * @return the Y coordinate + */ + public float getCenterY() { + synchronized (mLock) { + return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale(); + } + } + + /** * Returns the scale currently used by the window manager. If an * animation is in progress, this reflects the current state of the * animation. @@ -165,7 +280,7 @@ class MagnificationController { * @return the scale currently used by the window manager */ public float getSentScale() { - return mSentMagnificationSpec.scale; + return mSpecAnimationBridge.mSentMagnificationSpec.scale; } /** @@ -176,7 +291,7 @@ class MagnificationController { * @return the X offset currently used by the window manager */ public float getSentOffsetX() { - return mSentMagnificationSpec.offsetX; + return mSpecAnimationBridge.mSentMagnificationSpec.offsetX; } /** @@ -187,7 +302,7 @@ class MagnificationController { * @return the Y offset currently used by the window manager */ public float getSentOffsetY() { - return mSentMagnificationSpec.offsetY; + return mSpecAnimationBridge.mSentMagnificationSpec.offsetY; } /** @@ -196,21 +311,24 @@ class MagnificationController { * * @param animate {@code true} to animate the transition, {@code false} * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ - public void reset(boolean animate) { - if (mTransformationAnimator.isRunning()) { - mTransformationAnimator.cancel(); + public boolean reset(boolean animate) { + synchronized (mLock) { + return resetLocked(animate); } - mCurrentMagnificationSpec.clear(); - if (animate) { - animateMagnificationSpec(mSentMagnificationSpec, - mCurrentMagnificationSpec); - } else { - setMagnificationSpec(mCurrentMagnificationSpec); + } + + private boolean resetLocked(boolean animate) { + final MagnificationSpec spec = mCurrentMagnificationSpec; + final boolean changed = !spec.isNop(); + if (changed) { + spec.clear(); } - final Rect bounds = mTempRect; - bounds.setEmpty(); - mAms.onMagnificationStateChanged(); + + mSpecAnimationBridge.updateSentSpec(spec, animate); + return changed; } /** @@ -219,23 +337,32 @@ class MagnificationController { * transition is immediate. * * @param scale the target scale, must be >= 1 + * @param pivotX the screen-relative X coordinate around which to scale + * @param pivotY the screen-relative Y coordinate around which to scale * @param animate {@code true} to animate the transition, {@code false} * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ - public void setScale(float scale, float pivotX, float pivotY, boolean animate) { - final Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final MagnificationSpec spec = mCurrentMagnificationSpec; - final float oldScale = spec.scale; - final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale; - final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale; - final float normPivotX = (-spec.offsetX + pivotX) / oldScale; - final float normPivotY = (-spec.offsetY + pivotY) / oldScale; - final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); - final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); - final float centerX = normPivotX + offsetX; - final float centerY = normPivotY + offsetY; - setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate); + public boolean setScale(float scale, float pivotX, float pivotY, boolean animate) { + synchronized (mLock) { + // Constrain scale immediately for use in the pivot calculations. + scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); + + final Rect viewport = mTempRect; + mMagnifiedRegion.getBounds(viewport); + final MagnificationSpec spec = mCurrentMagnificationSpec; + final float oldScale = spec.scale; + final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale; + final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale; + final float normPivotX = (pivotX - spec.offsetX) / oldScale; + final float normPivotY = (pivotY - spec.offsetY) / oldScale; + final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); + final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); + final float centerX = normPivotX + offsetX; + final float centerY = normPivotY + offsetY; + return setScaleAndCenterLocked(scale, centerX, centerY, animate); + } } /** @@ -248,10 +375,13 @@ class MagnificationController { * center * @param animate {@code true} to animate the transition, {@code false} * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ - public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) { - setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY, - animate); + public boolean setCenter(float centerX, float centerY, boolean animate) { + synchronized (mLock) { + return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate); + } } /** @@ -259,35 +389,27 @@ class MagnificationController { * animating the transition. If animation is disabled, the transition * is immediate. * - * @param scale the target scale, must be >= 1 + * @param scale the target scale, or {@link Float#NaN} to leave unchanged * @param centerX the screen-relative X coordinate around which to - * center and scale + * center and scale, or {@link Float#NaN} to leave unchanged * @param centerY the screen-relative Y coordinate around which to - * center and scale + * center and scale, or {@link Float#NaN} to leave unchanged * @param animate {@code true} to animate the transition, {@code false} * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ - public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY, - boolean animate) { - if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0 - && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0 - && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) { - return; - } - if (mTransformationAnimator.isRunning()) { - mTransformationAnimator.cancel(); + public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) { + synchronized (mLock) { + return setScaleAndCenterLocked(scale, centerX, centerY, animate); } - if (DEBUG_MAGNIFICATION_CONTROLLER) { - Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY); - } - updateMagnificationSpec(scale, centerX, centerY); - if (animate) { - animateMagnificationSpec(mSentMagnificationSpec, - mCurrentMagnificationSpec); - } else { - setMagnificationSpec(mCurrentMagnificationSpec); - } - mAms.onMagnificationStateChanged(); + } + + private boolean setScaleAndCenterLocked( + float scale, float centerX, float centerY, boolean animate) { + final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); + mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate); + return changed; } /** @@ -297,75 +419,504 @@ class MagnificationController { * @param offsetY the amount in pixels to offset the Y center */ public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) { - final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; - mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, - getMinOffsetX()), 0); - final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; - mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, - getMinOffsetY()), 0); - setMagnificationSpec(mCurrentMagnificationSpec); - } - - private void updateMagnificationSpec(float scale, float magnifiedCenterX, - float magnifiedCenterY) { - final Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - mCurrentMagnificationSpec.scale = scale; - final int viewportWidth = magnifiedFrame.width(); - final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale; - mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, - getMinOffsetX()), 0); - final int viewportHeight = magnifiedFrame.height(); - final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale; - mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, - getMinOffsetY()), 0); - } - - private float getMinOffsetX() { - final Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float viewportWidth = magnifiedFrame.width(); + synchronized (mLock) { + final MagnificationSpec currSpec = mCurrentMagnificationSpec; + final float nonNormOffsetX = currSpec.offsetX - offsetX; + currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); + final float nonNormOffsetY = currSpec.offsetY - offsetY; + currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0); + mSpecAnimationBridge.updateSentSpec(currSpec, false); + } + } + + /** + * Persists the current magnification scale to the current user's settings. + */ + public void persistScale() { + final float scale = mCurrentMagnificationSpec.scale; + final int userId = mUserId; + + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + Settings.Secure.putFloatForUser(mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId); + return null; + } + }.execute(); + } + + /** + * Retrieves a previously persisted magnification scale from the current + * user's settings. + * + * @return the previously persisted magnification scale, or the default + * scale if none is available + */ + public float getPersistedScale() { + return Settings.Secure.getFloatForUser(mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, + DEFAULT_MAGNIFICATION_SCALE, mUserId); + } + + /** + * Updates the current magnification spec. + * + * @param scale the magnification scale + * @param centerX the unscaled, screen-relative X coordinate of the center + * of the viewport, or {@link Float#NaN} to leave unchanged + * @param centerY the unscaled, screen-relative Y coordinate of the center + * of the viewport, or {@link Float#NaN} to leave unchanged + * @return {@code true} if the magnification spec changed or {@code false} + * otherwise + */ + private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) { + if (!availableRegionContains(centerX, centerY)) { + return false; + } + + boolean changed = false; + + final MagnificationSpec currSpec = mCurrentMagnificationSpec; + + // Handle scale. + if (Float.isNaN(scale)) { + scale = getScale(); + } + + final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); + if (Float.compare(currSpec.scale, normScale) != 0) { + currSpec.scale = normScale; + changed = true; + } + + // Handle X offset. + if (Float.isNaN(centerX)) { + centerX = getCenterX(); + } + + final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale; + final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); + if (Float.compare(currSpec.offsetX, offsetX) != 0) { + currSpec.offsetX = offsetX; + changed = true; + } + + // Handle Y offset. + if (Float.isNaN(centerY)) { + centerY = getCenterY(); + } + + final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale; + final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0); + if (Float.compare(currSpec.offsetY, offsetY) != 0) { + currSpec.offsetY = offsetY; + changed = true; + } + + return changed; + } + + private float getMinOffsetXLocked() { + final float viewportWidth = mMagnifiedBounds.width(); return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale; } - private float getMinOffsetY() { - final Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float viewportHeight = magnifiedFrame.height(); + private float getMinOffsetYLocked() { + final float viewportHeight = mMagnifiedBounds.height(); return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale; } - private void animateMagnificationSpec(MagnificationSpec fromSpec, - MagnificationSpec toSpec) { - mTransformationAnimator.setObjectValues(fromSpec, toSpec); - mTransformationAnimator.start(); + /** + * Sets the currently active user ID. + * + * @param userId the currently active user ID + */ + public void setUserId(int userId) { + if (mUserId != userId) { + mUserId = userId; + + synchronized (mLock) { + if (isMagnifying()) { + reset(false); + } + } + } + } + + private boolean isScreenMagnificationAutoUpdateEnabled() { + return (Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, + DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); } - public void setMagnificationSpec(MagnificationSpec spec) { - if (DEBUG_SET_MAGNIFICATION_SPEC) { - Slog.i(LOG_TAG, "Sending: " + spec); + /** + * Resets magnification if magnification and auto-update are both enabled. + * + * @param animate whether the animate the transition + * @return {@code true} if magnification was reset to the disabled state, + * {@code false} if magnification is still active + */ + boolean resetIfNeeded(boolean animate) { + synchronized (mLock) { + if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) { + reset(animate); + return true; + } + return false; + } + } + + private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) { + final float scale = getSentScale(); + final float offsetX = getSentOffsetX(); + final float offsetY = getSentOffsetY(); + getMagnifiedBounds(outFrame); + outFrame.offset((int) -offsetX, (int) -offsetY); + outFrame.scale(1.0f / scale); + } + + private void requestRectangleOnScreen(int left, int top, int right, int bottom) { + synchronized (mLock) { + final Rect magnifiedFrame = mTempRect; + getMagnifiedBounds(magnifiedFrame); + if (!magnifiedFrame.intersects(left, top, right, bottom)) { + return; + } + + final Rect magnifFrameInScreenCoords = mTempRect1; + getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords); + + final float scrollX; + final float scrollY; + if (right - left > magnifFrameInScreenCoords.width()) { + final int direction = TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()); + if (direction == View.LAYOUT_DIRECTION_LTR) { + scrollX = left - magnifFrameInScreenCoords.left; + } else { + scrollX = right - magnifFrameInScreenCoords.right; + } + } else if (left < magnifFrameInScreenCoords.left) { + scrollX = left - magnifFrameInScreenCoords.left; + } else if (right > magnifFrameInScreenCoords.right) { + scrollX = right - magnifFrameInScreenCoords.right; + } else { + scrollX = 0; + } + + if (bottom - top > magnifFrameInScreenCoords.height()) { + scrollY = top - magnifFrameInScreenCoords.top; + } else if (top < magnifFrameInScreenCoords.top) { + scrollY = top - magnifFrameInScreenCoords.top; + } else if (bottom > magnifFrameInScreenCoords.bottom) { + scrollY = bottom - magnifFrameInScreenCoords.bottom; + } else { + scrollY = 0; + } + + final float scale = getScale(); + offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale); + } + } + + /** + * Class responsible for animating spec on the main thread and sending spec + * updates to the window manager. + */ + private static class SpecAnimationBridge { + private static final int ACTION_UPDATE_SPEC = 1; + + private final Handler mHandler; + private final WindowManagerInternal mWindowManager; + + /** + * The magnification spec that was sent to the window manager. This should + * only be accessed and modified on the main (e.g. animation) thread. + */ + private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); + + /** + * The animator that updates the sent spec. This should only be accessed + * and modified on the main (e.g. animation) thread. + */ + private final ValueAnimator mTransformationAnimator; + + private final long mMainThreadId; + + private SpecAnimationBridge(Context context) { + final Looper mainLooper = context.getMainLooper(); + mMainThreadId = mainLooper.getThread().getId(); + + mHandler = new UpdateHandler(context); + mWindowManager = LocalServices.getService(WindowManagerInternal.class); + + final MagnificationSpecProperty property = new MagnificationSpecProperty(); + final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator(); + final long animationDuration = context.getResources().getInteger( + R.integer.config_longAnimTime); + mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator, + mSentMagnificationSpec); + mTransformationAnimator.setDuration(animationDuration); + mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); + } + + public void updateSentSpec(MagnificationSpec spec, boolean animate) { + if (Thread.currentThread().getId() == mMainThreadId) { + // Already on the main thread, don't bother proxying. + updateSentSpecInternal(spec, animate); + } else { + mHandler.obtainMessage(ACTION_UPDATE_SPEC, + animate ? 1 : 0, 0, spec).sendToTarget(); + } + } + + /** + * Updates the sent spec. + */ + private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) { + if (mTransformationAnimator.isRunning()) { + mTransformationAnimator.cancel(); + } + + // If the current and sent specs don't match, update the sent spec. + final boolean changed = !mSentMagnificationSpec.equals(spec); + if (changed) { + if (animate) { + animateMagnificationSpec(spec); + } else { + setMagnificationSpec(spec); + } + } + } + + private void animateMagnificationSpec(MagnificationSpec toSpec) { + mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec); + mTransformationAnimator.start(); + } + + private void setMagnificationSpec(MagnificationSpec spec) { + if (DEBUG_SET_MAGNIFICATION_SPEC) { + Slog.i(LOG_TAG, "Sending: " + spec); + } + + mSentMagnificationSpec.setTo(spec); + mWindowManager.setMagnificationSpec(spec); + } + + private class UpdateHandler extends Handler { + public UpdateHandler(Context context) { + super(context.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case ACTION_UPDATE_SPEC: + final boolean animate = msg.arg1 == 1; + final MagnificationSpec spec = (MagnificationSpec) msg.obj; + updateSentSpecInternal(spec, animate); + break; + } + } + } + + private static class MagnificationSpecProperty + extends Property<SpecAnimationBridge, MagnificationSpec> { + public MagnificationSpecProperty() { + super(MagnificationSpec.class, "spec"); + } + + @Override + public MagnificationSpec get(SpecAnimationBridge object) { + return object.mSentMagnificationSpec; + } + + @Override + public void set(SpecAnimationBridge object, MagnificationSpec value) { + object.setMagnificationSpec(value); + } + } + + private static class MagnificationSpecEvaluator + implements TypeEvaluator<MagnificationSpec> { + private final MagnificationSpec mTempSpec = MagnificationSpec.obtain(); + + @Override + public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, + MagnificationSpec toSpec) { + final MagnificationSpec result = mTempSpec; + result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction; + result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction; + result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction; + return result; + } } - mSentMagnificationSpec.scale = spec.scale; - mSentMagnificationSpec.offsetX = spec.offsetX; - mSentMagnificationSpec.offsetY = spec.offsetY; - mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec)); } - public MagnificationSpec getMagnificationSpec() { - return mSentMagnificationSpec; + private static class ScreenStateObserver extends BroadcastReceiver { + private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; + + private final Context mContext; + private final MagnificationController mController; + private final Handler mHandler; + + public ScreenStateObserver(Context context, MagnificationController controller) { + mContext = context; + mController = controller; + mHandler = new StateChangeHandler(context); + } + + public void register() { + mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + } + + public void unregister() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, + intent.getAction()).sendToTarget(); + } + + private void handleOnScreenStateChange() { + mController.resetIfNeeded(false); + } + + private class StateChangeHandler extends Handler { + public StateChangeHandler(Context context) { + super(context.getMainLooper()); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ON_SCREEN_STATE_CHANGE: + handleOnScreenStateChange(); + break; + } + } + } } - private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> { - private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain(); + /** + * This class handles the screen magnification when accessibility is enabled. + */ + private static class WindowStateObserver + implements WindowManagerInternal.MagnificationCallbacks { + private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; + private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; + private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3; + private static final int MESSAGE_ON_ROTATION_CHANGED = 4; + + private final Rect mTempRect = new Rect(); + private final Rect mTempRect1 = new Rect(); + + private final MagnificationController mController; + private final WindowManagerInternal mWindowManager; + private final Handler mHandler; + + private boolean mSpecIsDirty; + + public WindowStateObserver(Context context, MagnificationController controller) { + mController = controller; + mWindowManager = LocalServices.getService(WindowManagerInternal.class); + mHandler = new CallbackHandler(context); + } + + public void register() { + mWindowManager.setMagnificationCallbacks(this); + } + + public void unregister() { + mWindowManager.setMagnificationCallbacks(null); + } @Override - public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, - MagnificationSpec toSpec) { - final MagnificationSpec result = mTempTransformationSpec; - result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction; - result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction; - result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction; - return result; + public void onMagnifiedBoundsChanged(Region magnified, Region available) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = Region.obtain(magnified); + args.arg2 = Region.obtain(available); + mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget(); + } + + private void handleOnMagnifiedBoundsChanged(Region magnified, Region available) { + mController.setMagnifiedRegion(magnified, available, mSpecIsDirty); + mSpecIsDirty = false; + } + + @Override + public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = left; + args.argi2 = top; + args.argi3 = right; + args.argi4 = bottom; + mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget(); + } + + private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) { + mController.requestRectangleOnScreen(left, top, right, bottom); + } + + @Override + public void onRotationChanged(int rotation) { + mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget(); + } + + private void handleOnRotationChanged() { + // If there was a rotation and magnification is still enabled, + // we'll need to rewrite the spec to reflect the new screen + // configuration. Conveniently, we'll receive a callback from + // the window manager with updated bounds for the magnified + // region. + mSpecIsDirty = !mController.resetIfNeeded(true); + } + + @Override + public void onUserContextChanged() { + mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED); + } + + private void handleOnUserContextChanged() { + mController.resetIfNeeded(true); + } + + private class CallbackHandler extends Handler { + public CallbackHandler(Context context) { + super(context.getMainLooper()); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: { + final SomeArgs args = (SomeArgs) message.obj; + final Region magnifiedBounds = (Region) args.arg1; + final Region availableBounds = (Region) args.arg2; + handleOnMagnifiedBoundsChanged(magnifiedBounds, availableBounds); + magnifiedBounds.recycle(); + availableBounds.recycle(); + } break; + case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { + final SomeArgs args = (SomeArgs) message.obj; + final int left = args.argi1; + final int top = args.argi2; + final int right = args.argi3; + final int bottom = args.argi4; + handleOnRectangleOnScreenRequested(left, top, right, bottom); + args.recycle(); + } break; + case MESSAGE_ON_USER_CONTEXT_CHANGED: { + handleOnUserContextChanged(); + } break; + case MESSAGE_ON_ROTATION_CHANGED: { + handleOnRotationChanged(); + } break; + } + } } } } diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 8feb167f4c99..51c8ab572350 100644 --- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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. @@ -16,18 +16,10 @@ package com.android.server.accessibility; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Rect; -import android.graphics.Region; -import android.os.AsyncTask; -import android.os.Binder; import android.os.Handler; import android.os.Message; -import android.provider.Settings; -import android.text.TextUtils; +import android.util.MathUtils; import android.util.Slog; import android.util.TypedValue; import android.view.GestureDetector; @@ -39,18 +31,12 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; -import android.view.View; import android.view.ViewConfiguration; -import android.view.WindowManagerInternal; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.os.SomeArgs; -import com.android.server.LocalServices; - -import java.util.Locale; - /** - * This class handles the screen magnification when accessibility is enabled. + * This class handles magnification in response to touch events. + * * The behavior is as follows: * * 1. Triple tap toggles permanent screen magnification which is magnifying @@ -88,10 +74,8 @@ import java.util.Locale; * * 6. The magnification scale will be persisted in settings and in the cloud. */ -public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks, - EventStreamTransformation { - - private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); +class MagnificationGestureHandler implements EventStreamTransformation { + private static final String LOG_TAG = "MagnificationEventHandler"; private static final boolean DEBUG_STATE_TRANSITIONS = false; private static final boolean DEBUG_DETECTING = false; @@ -103,40 +87,19 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private static final int STATE_VIEWPORT_DRAGGING = 3; private static final int STATE_MAGNIFIED_INTERACTION = 4; - private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; - - private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; - private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; - private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3; - private static final int MESSAGE_ON_ROTATION_CHANGED = 4; + private static final float MIN_SCALE = 2.0f; + private static final float MAX_SCALE = 5.0f; - private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; - - private static final int MY_PID = android.os.Process.myPid(); - - private final Rect mTempRect = new Rect(); - private final Rect mTempRect1 = new Rect(); - - private final Context mContext; - private final WindowManagerInternal mWindowManager; private final MagnificationController mMagnificationController; - private final ScreenStateObserver mScreenStateObserver; - private final DetectingStateHandler mDetectingStateHandler; - private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler; + private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler; private final StateViewportDraggingHandler mStateViewportDraggingHandler; - private final int mUserId; - - private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout(); - private final int mMultiTapTimeSlop; - private final int mTapDistanceSlop; - private final int mMultiTapDistanceSlop; - private EventStreamTransformation mNext; private int mCurrentState; private int mPreviousState; + private boolean mTranslationEnabledBeforePan; private PointerCoords[] mTempPointerCoords; @@ -144,189 +107,44 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private long mDelegatingStateDownTime; - private boolean mUpdateMagnificationSpecOnNextBoundsChange; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: { - Region bounds = (Region) message.obj; - handleOnMagnifiedBoundsChanged(bounds); - bounds.recycle(); - } break; - case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { - SomeArgs args = (SomeArgs) message.obj; - final int left = args.argi1; - final int top = args.argi2; - final int right = args.argi3; - final int bottom = args.argi4; - handleOnRectangleOnScreenRequested(left, top, right, bottom); - args.recycle(); - } break; - case MESSAGE_ON_USER_CONTEXT_CHANGED: { - handleOnUserContextChanged(); - } break; - case MESSAGE_ON_ROTATION_CHANGED: { - final int rotation = message.arg1; - handleOnRotationChanged(rotation); - } break; - } - } - }; - - public ScreenMagnifier(Context context, int userId, int displayId, - AccessibilityManagerService service) { - mContext = context; - mUserId = userId; - mWindowManager = LocalServices.getService(WindowManagerInternal.class); - - mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout() - + mContext.getResources().getInteger( - com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); - mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); - - mDetectingStateHandler = new DetectingStateHandler(); + public MagnificationGestureHandler(Context context, AccessibilityManagerService ams) { + mMagnificationController = ams.getMagnificationController(); + mDetectingStateHandler = new DetectingStateHandler(context); mStateViewportDraggingHandler = new StateViewportDraggingHandler(); - mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler( - context); - - mMagnificationController = service.getMagnificationController(); - mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController); - - mWindowManager.setMagnificationCallbacks(this); + mMagnifiedContentInteractionStateHandler = + new MagnifiedContentInteractionStateHandler(context); transitionToState(STATE_DETECTING); } @Override - public void onMagnifedBoundsChanged(Region bounds) { - Region newBounds = Region.obtain(bounds); - mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget(); - if (MY_PID != Binder.getCallingPid()) { - bounds.recycle(); - } - } - - private void handleOnMagnifiedBoundsChanged(Region bounds) { - // If there was a rotation we have to update the center of the magnified - // region since the old offset X/Y may be out of its acceptable range for - // the new display width and height. - mMagnificationController.setMagnifiedRegion( - bounds, mUpdateMagnificationSpecOnNextBoundsChange); - mUpdateMagnificationSpecOnNextBoundsChange = false; - } - - @Override - public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = left; - args.argi2 = top; - args.argi3 = right; - args.argi4 = bottom; - mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget(); - } - - private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) { - Rect magnifiedFrame = mTempRect; - mMagnificationController.getMagnifiedBounds(magnifiedFrame); - if (!magnifiedFrame.intersects(left, top, right, bottom)) { - return; - } - Rect magnifFrameInScreenCoords = mTempRect1; - getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords); - final float scrollX; - final float scrollY; - if (right - left > magnifFrameInScreenCoords.width()) { - final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); - if (direction == View.LAYOUT_DIRECTION_LTR) { - scrollX = left - magnifFrameInScreenCoords.left; - } else { - scrollX = right - magnifFrameInScreenCoords.right; - } - } else if (left < magnifFrameInScreenCoords.left) { - scrollX = left - magnifFrameInScreenCoords.left; - } else if (right > magnifFrameInScreenCoords.right) { - scrollX = right - magnifFrameInScreenCoords.right; - } else { - scrollX = 0; - } - if (bottom - top > magnifFrameInScreenCoords.height()) { - scrollY = top - magnifFrameInScreenCoords.top; - } else if (top < magnifFrameInScreenCoords.top) { - scrollY = top - magnifFrameInScreenCoords.top; - } else if (bottom > magnifFrameInScreenCoords.bottom) { - scrollY = bottom - magnifFrameInScreenCoords.bottom; - } else { - scrollY = 0; - } - final float scale = mMagnificationController.getScale(); - mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale); - } - - @Override - public void onRotationChanged(int rotation) { - mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget(); - } - - private void handleOnRotationChanged(int rotation) { - resetMagnificationIfNeeded(); - if (mMagnificationController.isMagnifying()) { - mUpdateMagnificationSpecOnNextBoundsChange = true; - } - } - - @Override - public void onUserContextChanged() { - mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED); - } - - private void handleOnUserContextChanged() { - resetMagnificationIfNeeded(); - } - - private void getMagnifiedFrameInContentCoords(Rect rect) { - final float scale = mMagnificationController.getSentScale(); - final float offsetX = mMagnificationController.getSentOffsetX(); - final float offsetY = mMagnificationController.getSentOffsetY(); - mMagnificationController.getMagnifiedBounds(rect); - rect.offset((int) -offsetX, (int) -offsetY); - rect.scale(1.0f / scale); - } - - private void resetMagnificationIfNeeded() { - if (mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(true); - } - } - - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { if (mNext != null) { mNext.onMotionEvent(event, rawEvent, policyFlags); } return; } - mMagnifiedContentInteractonStateHandler.onMotionEvent(event); + mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags); switch (mCurrentState) { case STATE_DELEGATING: { handleMotionEventStateDelegating(event, rawEvent, policyFlags); - } break; + } + break; case STATE_DETECTING: { mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags); - } break; + } + break; case STATE_VIEWPORT_DRAGGING: { - mStateViewportDraggingHandler.onMotionEvent(event, policyFlags); - } break; + mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags); + } + break; case STATE_MAGNIFIED_INTERACTION: { // mMagnifiedContentInteractonStateHandler handles events only // if this is the current state since it uses ScaleGestureDetecotr // and a GestureDetector which need well formed event stream. - } break; + } + break; default: { throw new IllegalStateException("Unknown state: " + mCurrentState); } @@ -336,7 +154,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @Override public void onKeyEvent(KeyEvent event, int policyFlags) { if (mNext != null) { - mNext.onKeyEvent(event, policyFlags); + mNext.onKeyEvent(event, policyFlags); } } @@ -366,15 +184,13 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @Override public void onDestroy() { clear(); - mScreenStateObserver.destroy(); - mWindowManager.setMagnificationCallbacks(null); } private void clear() { mCurrentState = STATE_DETECTING; mDetectingStateHandler.clear(); mStateViewportDraggingHandler.clear(); - mMagnifiedContentInteractonStateHandler.clear(); + mMagnifiedContentInteractionStateHandler.clear(); } private void handleMotionEventStateDelegating(MotionEvent event, @@ -382,12 +198,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDelegatingStateDownTime = event.getDownTime(); - } break; + } + break; case MotionEvent.ACTION_UP: { if (mDetectingStateHandler.mDelayedEventQueue == null) { transitionToState(STATE_DETECTING); } - } break; + } + break; } if (mNext != null) { // If the event is within the magnified portion of the screen we have @@ -402,7 +220,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio final float scaledOffsetY = mMagnificationController.getOffsetY(); final int pointerCount = event.getPointerCount(); PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); - PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); + PointerProperties[] properties = getTempPointerPropertiesWithMinSize( + pointerCount); for (int i = 0; i < pointerCount; i++) { event.getPointerCoords(i, coords[i]); coords[i].x = (coords[i].x - scaledOffsetX) / scale; @@ -441,12 +260,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { - final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0; + final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length + : 0; if (oldSize < size) { PointerProperties[] oldTempPointerProperties = mTempPointerProperties; mTempPointerProperties = new PointerProperties[size]; if (oldTempPointerProperties != null) { - System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); + System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, + oldSize); } } for (int i = oldSize; i < size; i++) { @@ -460,16 +281,20 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio switch (state) { case STATE_DELEGATING: { Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); - } break; + } + break; case STATE_DETECTING: { Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); - } break; + } + break; case STATE_VIEWPORT_DRAGGING: { Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); - } break; + } + break; case STATE_MAGNIFIED_INTERACTION: { Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown state: " + state); } @@ -479,20 +304,30 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mCurrentState = state; } - private final class MagnifiedContentInteractonStateHandler - extends SimpleOnGestureListener implements OnScaleGestureListener { - private static final float MIN_SCALE = 1.3f; - private static final float MAX_SCALE = 5.0f; + private interface MotionEventHandler { + + void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); + + void clear(); + } + + /** + * This class determines if the user is performing a scale or pan gesture. + */ + private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener + implements OnScaleGestureListener, MotionEventHandler { private final ScaleGestureDetector mScaleGestureDetector; + private final GestureDetector mGestureDetector; private final float mScalingThreshold; private float mInitialScaleFactor = -1; + private boolean mScaling; - public MagnifiedContentInteractonStateHandler(Context context) { + public MagnifiedContentInteractionStateHandler(Context context) { final TypedValue scaleValue = new TypedValue(); context.getResources().getValue( com.android.internal.R.dimen.config_screen_magnification_scaling_threshold, @@ -503,7 +338,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mGestureDetector = new GestureDetector(context, this); } - public void onMotionEvent(MotionEvent event) { + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { mScaleGestureDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { @@ -511,11 +347,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } if (event.getActionMasked() == MotionEvent.ACTION_UP) { clear(); - final float scale = Math.min(Math.max(mMagnificationController.getScale(), - MIN_SCALE), MAX_SCALE); - if (scale != getPersistedScale()) { - persistScale(scale); - } + mMagnificationController.persistScale(); if (mPreviousState == STATE_VIEWPORT_DRAGGING) { transitionToState(STATE_VIEWPORT_DRAGGING); } else { @@ -552,14 +384,29 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } return false; } - final float newScale = mMagnificationController.getScale() - * detector.getScaleFactor(); - final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE); - if (DEBUG_SCALING) { - Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale); + + final float initialScale = mMagnificationController.getScale(); + final float targetScale = initialScale * detector.getScaleFactor(); + + // Don't allow a gesture to move the user further outside the + // desired bounds for gesture-controlled scaling. + final float scale; + if (targetScale > MAX_SCALE && targetScale > initialScale) { + // The target scale is too big and getting bigger. + scale = MAX_SCALE; + } else if (targetScale < MIN_SCALE && targetScale < initialScale) { + // The target scale is too small and getting smaller. + scale = MIN_SCALE; + } else { + // The target scale may be outside our bounds, but at least + // it's moving in the right direction. This avoids a "jump" if + // we're at odds with some other service's desired bounds. + scale = targetScale; } - mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(), - detector.getFocusY(), false); + + final float pivotX = detector.getFocusX(); + final float pivotY = detector.getFocusY(); + mMagnificationController.setScale(scale, pivotX, pivotY, false); return true; } @@ -573,16 +420,24 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio clear(); } - private void clear() { + @Override + public void clear() { mInitialScaleFactor = -1; mScaling = false; } } - private final class StateViewportDraggingHandler { + /** + * This class handles motion events when the event dispatcher has + * determined that the user is performing a single-finger drag of the + * magnification viewport. + */ + private final class StateViewportDraggingHandler implements MotionEventHandler { + private boolean mLastMoveOutsideMagnifiedRegion; - private void onMotionEvent(MotionEvent event, int policyFlags) { + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: { @@ -591,7 +446,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio case MotionEvent.ACTION_POINTER_DOWN: { clear(); transitionToState(STATE_MAGNIFIED_INTERACTION); - } break; + } + break; case MotionEvent.ACTION_MOVE: { if (event.getPointerCount() != 1) { throw new IllegalStateException("Should have one pointer down."); @@ -601,35 +457,43 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) { if (mLastMoveOutsideMagnifiedRegion) { mLastMoveOutsideMagnifiedRegion = false; - mMagnificationController.setMagnifiedRegionCenter(eventX, + mMagnificationController.setCenter(eventX, eventY, true); } else { - mMagnificationController.setMagnifiedRegionCenter(eventX, + mMagnificationController.setCenter(eventX, eventY, false); } } else { mLastMoveOutsideMagnifiedRegion = true; } - } break; + } + break; case MotionEvent.ACTION_UP: { if (!mTranslationEnabledBeforePan) { mMagnificationController.reset(true); } clear(); transitionToState(STATE_DETECTING); - } break; + } + break; case MotionEvent.ACTION_POINTER_UP: { - throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP"); + throw new IllegalArgumentException( + "Unexpected event type: ACTION_POINTER_UP"); } } } + @Override public void clear() { mLastMoveOutsideMagnifiedRegion = false; } } - private final class DetectingStateHandler { + /** + * This class handles motion events when the event dispatch has not yet + * determined what the user is doing. It watches for various tap events. + */ + private final class DetectingStateHandler implements MotionEventHandler { private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; @@ -637,12 +501,30 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private static final int ACTION_TAP_COUNT = 3; + private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout(); + + private final int mMultiTapTimeSlop; + + private final int mTapDistanceSlop; + + private final int mMultiTapDistanceSlop; + private MotionEventInfo mDelayedEventQueue; private MotionEvent mLastDownEvent; + private MotionEvent mLastTapUpEvent; + private int mTapCount; + public DetectingStateHandler(Context context) { + mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout() + + context.getResources().getInteger( + com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); + mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + } + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message message) { @@ -652,12 +534,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio MotionEvent event = (MotionEvent) message.obj; final int policyFlags = message.arg1; onActionTapAndHold(event, policyFlags); - } break; + } + break; case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { transitionToState(STATE_DELEGATING); sendDelayedMotionEvents(); clear(); - } break; + } + break; default: { throw new IllegalArgumentException("Unknown message type: " + type); } @@ -665,6 +549,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } }; + @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { cacheDelayedMotionEvent(event, rawEvent, policyFlags); final int action = event.getActionMasked(); @@ -678,7 +563,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null && GestureUtils.isMultiTap(mLastDownEvent, event, - mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, policyFlags, 0, event); mHandler.sendMessageDelayed(message, @@ -690,7 +575,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } clearLastDownEvent(); mLastDownEvent = MotionEvent.obtain(event); - } break; + } + break; case MotionEvent.ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { transitionToState(STATE_MAGNIFIED_INTERACTION); @@ -698,7 +584,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } else { transitionToDelegatingStateAndClear(); } - } break; + } + break; case MotionEvent.ACTION_MOVE: { if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { final double distance = GestureUtils.computeDistance(mLastDownEvent, @@ -707,7 +594,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio transitionToDelegatingStateAndClear(); } } - } break; + } + break; case MotionEvent.ACTION_UP: { if (mLastDownEvent == null) { return; @@ -715,8 +603,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!mMagnificationController.magnifiedRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); - return; + transitionToDelegatingStateAndClear(); + return; } if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, mTapDistanceSlop, 0)) { @@ -739,13 +627,16 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } clearLastTapUpEvent(); mLastTapUpEvent = MotionEvent.obtain(event); - } break; + } + break; case MotionEvent.ACTION_POINTER_UP: { /* do nothing */ - } break; + } + break; } } + @Override public void clear() { mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); @@ -792,7 +683,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio while (mDelayedEventQueue != null) { MotionEventInfo info = mDelayedEventQueue; mDelayedEventQueue = info.mNext; - ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent, + MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent, info.mPolicyFlags); info.recycle(); } @@ -816,9 +707,11 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio if (DEBUG_DETECTING) { Slog.i(LOG_TAG, "onActionTap()"); } + if (!mMagnificationController.isMagnifying()) { - mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), - up.getX(), up.getY(), true); + final float targetScale = mMagnificationController.getPersistedScale(); + final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); + mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true); } else { mMagnificationController.reset(true); } @@ -828,35 +721,16 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio if (DEBUG_DETECTING) { Slog.i(LOG_TAG, "onActionTapAndHold()"); } + clear(); mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); - mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), - down.getX(), down.getY(), true); - transitionToState(STATE_VIEWPORT_DRAGGING); - } - } - private void persistScale(final float scale) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - Settings.Secure.putFloatForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId); - return null; - } - }.execute(); - } + final float targetScale = mMagnificationController.getPersistedScale(); + final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); + mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true); - private float getPersistedScale() { - return Settings.Secure.getFloatForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, - DEFAULT_MAGNIFICATION_SCALE, mUserId); - } - - private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) { - return (Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, - DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); + transitionToState(STATE_VIEWPORT_DRAGGING); + } } private static final class MotionEventInfo { @@ -864,14 +738,19 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private static final int MAX_POOL_SIZE = 10; private static final Object sLock = new Object(); + private static MotionEventInfo sPool; + private static int sPoolSize; private MotionEventInfo mNext; + private boolean mInPool; public MotionEvent mEvent; + public MotionEvent mRawEvent; + public int mPolicyFlags; public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent, @@ -922,47 +801,4 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mPolicyFlags = 0; } } - - private final class ScreenStateObserver extends BroadcastReceiver { - private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; - - private final Context mContext; - private final MagnificationController mMagnificationController; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ON_SCREEN_STATE_CHANGE: { - String action = (String) message.obj; - handleOnScreenStateChange(action); - } break; - } - } - }; - - public ScreenStateObserver(Context context, - MagnificationController magnificationController) { - mContext = context; - mMagnificationController = magnificationController; - mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - } - - public void destroy() { - mContext.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, - intent.getAction()).sendToTarget(); - } - - private void handleOnScreenStateChange(String action) { - if (mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(false); - } - } - } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index a5ddc12a2afc..f9d9950089b6 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -115,6 +115,7 @@ class AlarmManagerService extends SystemService { final LocalLog mLog = new LocalLog(TAG); AppOpsManager mAppOps; + DeviceIdleController.LocalService mLocalDeviceIdleController; final Object mLock = new Object(); @@ -897,6 +898,8 @@ class AlarmManagerService extends SystemService { if (phase == PHASE_SYSTEM_SERVICES_READY) { mConstants.start(getContext().getContentResolver()); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); + mLocalDeviceIdleController + = LocalServices.getService(DeviceIdleController.LocalService.class); } } @@ -2468,10 +2471,9 @@ class AlarmManagerService extends SystemService { private class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; - public static final int MINUTE_CHANGE_EVENT = 2; - public static final int DATE_CHANGE_EVENT = 3; - public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4; - public static final int LISTENER_TIMEOUT = 5; + public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; + public static final int LISTENER_TIMEOUT = 3; + public static final int REPORT_ALARMS_ACTIVE = 4; public AlarmHandler() { } @@ -2511,6 +2513,12 @@ class AlarmManagerService extends SystemService { mDeliveryTracker.alarmTimedOut((IBinder) msg.obj); break; + case REPORT_ALARMS_ACTIVE: + if (mLocalDeviceIdleController != null) { + mLocalDeviceIdleController.setAlarmsActive(msg.arg1 != 0); + } + break; + default: // nope, just ignore it break; @@ -2740,6 +2748,7 @@ class AlarmManagerService extends SystemService { } mBroadcastRefCount--; if (mBroadcastRefCount == 0) { + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget(); mWakeLock.release(); if (mInFlight.size() > 0) { mLog.w("Finished all dispatches with " + mInFlight.size() @@ -2873,6 +2882,7 @@ class AlarmManagerService extends SystemService { alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1, true); mWakeLock.acquire(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget(); } final InFlight inflight = new InFlight(AlarmManagerService.this, alarm.operation, alarm.listener, alarm.workSource, alarm.uid, diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 927b995e5cb0..485e26b7752a 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -48,6 +48,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; +import android.os.IBinder; import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; @@ -196,6 +197,12 @@ public class DeviceIdleController extends SystemService private long mNextIdleDelay; private long mNextLightAlarmTime; + private int mActiveIdleOpCount; + private IBinder mDownloadServiceActive; + private boolean mSyncActive; + private boolean mJobsActive; + private boolean mAlarmsActive; + public final AtomicFile mConfigFile; /** @@ -282,16 +289,22 @@ public class DeviceIdleController extends SystemService } } else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) { synchronized (DeviceIdleController.this) { - stepLightIdleStateLocked(); + stepLightIdleStateLocked("s:alarm"); } } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) { synchronized (DeviceIdleController.this) { - stepIdleStateLocked(); + stepIdleStateLocked("s:alarm"); } } } }; + private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + decActiveIdleOps(); + } + }; + private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @Override public void onDisplayAdded(int displayId) { @@ -733,7 +746,7 @@ public class DeviceIdleController extends SystemService // If we are currently sensing, it is time to move to locating. synchronized (this) { mNotMoving = true; - stepIdleStateLocked(); + stepIdleStateLocked("s:stationary"); } } else if (mState == STATE_LOCATING) { // If we are currently locating, note that we are not moving and step @@ -741,7 +754,7 @@ public class DeviceIdleController extends SystemService synchronized (this) { mNotMoving = true; if (mLocated) { - stepIdleStateLocked(); + stepIdleStateLocked("s:stationary"); } } } @@ -804,11 +817,18 @@ public class DeviceIdleController extends SystemService } catch (RemoteException e) { } if (fullChanged) { - getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + incActiveIdleOps(); + getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL, + null, mIdleStartedDoneReceiver, null, 0, null, null); } if (lightChanged) { - getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL); + incActiveIdleOps(); + getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL, + null, mIdleStartedDoneReceiver, null, 0, null, null); } + // Always start with one active op for the message being sent here. + // Now we we done! + decActiveIdleOps(); EventLogTags.writeDeviceIdleOffComplete(); } break; case MSG_REPORT_ACTIVE: { @@ -913,11 +933,23 @@ public class DeviceIdleController extends SystemService } @Override public void exitIdle(String reason) { - getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, null); exitIdleInternal(reason); } + @Override public void downloadServiceActive(IBinder token) { + getContext().enforceCallingOrSelfPermission( + "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null); + DeviceIdleController.this.downloadServiceActive(token); + } + + @Override public void downloadServiceInactive() { + getContext().enforceCallingOrSelfPermission( + "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null); + DeviceIdleController.this.downloadServiceInactive(); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { DeviceIdleController.this.dump(fd, pw, args); } @@ -937,6 +969,19 @@ public class DeviceIdleController extends SystemService public void setNetworkPolicyTempWhitelistCallback(Runnable callback) { setNetworkPolicyTempWhitelistCallbackInternal(callback); } + + public void setSyncActive(boolean active) { + DeviceIdleController.this.setSyncActive(active); + } + + public void setJobsActive(boolean active) { + DeviceIdleController.this.setJobsActive(active); + } + + // Up-call from alarm manager. + public void setAlarmsActive(boolean active) { + DeviceIdleController.this.setAlarmsActive(active); + } } public DeviceIdleController(Context context) { @@ -1439,7 +1484,7 @@ public class DeviceIdleController extends SystemService } } - void stepLightIdleStateLocked() { + void stepLightIdleStateLocked(String reason) { if (mLightState == LIGHT_STATE_OVERRIDE) { // If we are already in full device idle mode, then // there is nothing left to do for light mode. @@ -1455,22 +1500,23 @@ public class DeviceIdleController extends SystemService scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT); if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); mLightState = LIGHT_STATE_IDLE; - EventLogTags.writeDeviceIdleLight(mLightState, "step"); + EventLogTags.writeDeviceIdleLight(mLightState, reason); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); break; case LIGHT_STATE_IDLE: // We have been idling long enough, now it is time to do some work. + mActiveIdleOpCount = 1; scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT); if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE."); mLightState = LIGHT_STATE_IDLE_MAINTENANCE; - EventLogTags.writeDeviceIdleLight(mLightState, "step"); + EventLogTags.writeDeviceIdleLight(mLightState, reason); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break; } } - void stepIdleStateLocked() { + void stepIdleStateLocked(String reason) { if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState); EventLogTags.writeDeviceIdleStep(); @@ -1494,12 +1540,12 @@ public class DeviceIdleController extends SystemService mNextIdleDelay = mConstants.IDLE_TIMEOUT; mState = STATE_IDLE_PENDING; if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING."); - EventLogTags.writeDeviceIdle(mState, "step"); + EventLogTags.writeDeviceIdle(mState, reason); break; case STATE_IDLE_PENDING: mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); - EventLogTags.writeDeviceIdle(mState, "step"); + EventLogTags.writeDeviceIdle(mState, reason); scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false); cancelLocatingLocked(); mAnyMotionDetector.checkForAnyMotion(); @@ -1511,7 +1557,7 @@ public class DeviceIdleController extends SystemService case STATE_SENSING: mState = STATE_LOCATING; if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING."); - EventLogTags.writeDeviceIdle(mState, "step"); + EventLogTags.writeDeviceIdle(mState, reason); scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false); if (mLocationManager != null && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) { @@ -1553,23 +1599,101 @@ public class DeviceIdleController extends SystemService mLightState = LIGHT_STATE_OVERRIDE; cancelLightAlarmLocked(); } - EventLogTags.writeDeviceIdle(mState, "step"); + EventLogTags.writeDeviceIdle(mState, reason); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON); break; case STATE_IDLE: // We have been idling long enough, now it is time to do some work. + mActiveIdleOpCount = 1; scheduleAlarmLocked(mNextIdlePendingDelay, false); if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + "Next alarm in " + mNextIdlePendingDelay + " ms."); mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT, (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR)); mState = STATE_IDLE_MAINTENANCE; - EventLogTags.writeDeviceIdle(mState, "step"); + EventLogTags.writeDeviceIdle(mState, reason); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break; } } + void incActiveIdleOps() { + synchronized (this) { + mActiveIdleOpCount++; + } + } + + void decActiveIdleOps() { + synchronized (this) { + mActiveIdleOpCount--; + if (mActiveIdleOpCount <= 0) { + exitMaintenanceEarlyIfNeededLocked(); + } + } + } + + void downloadServiceActive(IBinder token) { + synchronized (this) { + mDownloadServiceActive = token; + try { + token.linkToDeath(new IBinder.DeathRecipient() { + @Override public void binderDied() { + downloadServiceInactive(); + } + }, 0); + } catch (RemoteException e) { + mDownloadServiceActive = null; + } + } + } + + void downloadServiceInactive() { + synchronized (this) { + mDownloadServiceActive = null; + exitMaintenanceEarlyIfNeededLocked(); + } + } + + void setSyncActive(boolean active) { + synchronized (this) { + mSyncActive = active; + if (!active) { + exitMaintenanceEarlyIfNeededLocked(); + } + } + } + + void setJobsActive(boolean active) { + synchronized (this) { + mJobsActive = active; + if (!active) { + exitMaintenanceEarlyIfNeededLocked(); + } + } + } + + void setAlarmsActive(boolean active) { + synchronized (this) { + mAlarmsActive = active; + if (!active) { + exitMaintenanceEarlyIfNeededLocked(); + } + } + } + + void exitMaintenanceEarlyIfNeededLocked() { + if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) { + if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null + && !mSyncActive && !mJobsActive && !mAlarmsActive) { + if (mState == STATE_IDLE_MAINTENANCE) { + stepIdleStateLocked("s:early"); + } else { + stepLightIdleStateLocked("s:early"); + } + } + } + } + void motionLocked() { if (DEBUG) Slog.d(TAG, "motionLocked()"); // The motion sensor will have been disabled at this point @@ -1612,7 +1736,7 @@ public class DeviceIdleController extends SystemService } mLocated = true; if (mNotMoving) { - stepIdleStateLocked(); + stepIdleStateLocked("s:location"); } } @@ -1628,7 +1752,7 @@ public class DeviceIdleController extends SystemService } mLocated = true; if (mNotMoving) { - stepIdleStateLocked(); + stepIdleStateLocked("s:gps"); } } @@ -1933,7 +2057,7 @@ public class DeviceIdleController extends SystemService long token = Binder.clearCallingIdentity(); try { exitForceIdleLocked(); - stepIdleStateLocked(); + stepIdleStateLocked("s:shell"); pw.print("Stepped to: "); pw.println(stateToString(mState)); } finally { @@ -1947,7 +2071,7 @@ public class DeviceIdleController extends SystemService long token = Binder.clearCallingIdentity(); try { exitForceIdleLocked(); - stepLightIdleStateLocked(); + stepLightIdleStateLocked("s:shell"); pw.print("Stepped to: "); pw.println(lightStateToString(mLightState)); } finally { Binder.restoreCallingIdentity(token); @@ -1967,7 +2091,7 @@ public class DeviceIdleController extends SystemService becomeInactiveIfAppropriateLocked(); int curState = mState; while (curState != STATE_IDLE) { - stepIdleStateLocked(); + stepIdleStateLocked("s:shell"); if (curState == mState) { pw.print("Unable to go idle; stopped at "); pw.println(stateToString(mState)); @@ -2226,6 +2350,9 @@ public class DeviceIdleController extends SystemService pw.println(lightStateToString(mLightState)); pw.print(" mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw); pw.println(); + if (mActiveIdleOpCount != 0) { + pw.print(" mActiveIdleOpCount="); pw.println(mActiveIdleOpCount); + } if (mNextAlarmTime != 0) { pw.print(" mNextAlarmTime="); TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw); @@ -2246,6 +2373,18 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw); pw.println(); } + if (mSyncActive) { + pw.print(" mSyncActive="); pw.println(mSyncActive); + } + if (mJobsActive) { + pw.print(" mJobsActive="); pw.println(mJobsActive); + } + if (mAlarmsActive) { + pw.print(" mAlarmsActive="); pw.println(mAlarmsActive); + } + if (mDownloadServiceActive != null) { + pw.print(" mDownloadServiceActive="); pw.println(mDownloadServiceActive); + } } } } diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 3359060d3fe7..43d10c74478c 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -226,7 +226,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { final int N = a.length; boolean printedHeader = false; F filter; - if (collapseDuplicates) { + if (collapseDuplicates && !printFilter) { found.clear(); for (int i=0; i<N && (filter=a[i]) != null; i++) { if (packageName != null && !isPackageForFilter(packageName, filter)) { diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 37dd884c8dc7..f89155d93d23 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -56,6 +56,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -2768,6 +2769,12 @@ class MountService extends IMountService.Stub } @Override + public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { + // TODO: Invoke vold to mount app fuse. + throw new UnsupportedOperationException(); + } + + @Override public int mkdirs(String callingPkg, String appPath) { final int userId = UserHandle.getUserId(Binder.getCallingUid()); final UserEnvironment userEnv = new UserEnvironment(userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 566065c02852..c1413ff4abd9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3337,15 +3337,6 @@ public final class ActivityManagerService extends ActivityManagerNative if ("1".equals(SystemProperties.get("debug.checkjni"))) { debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } - String jitDebugProperty = SystemProperties.get("debug.usejit"); - if ("true".equals(jitDebugProperty)) { - debugFlags |= Zygote.DEBUG_ENABLE_JIT; - } else if (!"false".equals(jitDebugProperty)) { - // If we didn't force disable by setting false, defer to the dalvik vm options. - if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) { - debugFlags |= Zygote.DEBUG_ENABLE_JIT; - } - } String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info"); if ("true".equals(genDebugInfoProperty)) { debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; @@ -18894,8 +18885,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - Process.setSwappiness(app.pid, - app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE); } } if (app.repForegroundActivities != app.foregroundActivities) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d1e7e85b6d51..13c14176e670 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -73,7 +73,7 @@ class ActivityManagerShellCommand extends ShellCommand { String opt; while ((opt = getNextOption()) != null) { if (opt.equals("--user")) { - userId = parseUserArg(getNextArgRequired()); + userId = UserHandle.parseUserArg(getNextArgRequired()); } else { pw.println("Error: Unknown option: " + opt); return -1; @@ -89,7 +89,7 @@ class ActivityManagerShellCommand extends ShellCommand { String opt; while ((opt=getNextOption()) != null) { if (opt.equals("--user")) { - userId = parseUserArg(getNextArgRequired()); + userId = UserHandle.parseUserArg(getNextArgRequired()); } else { pw.println("Error: Unknown option: " + opt); return -1; @@ -141,22 +141,6 @@ class ActivityManagerShellCommand extends ShellCommand { return 0; } - int parseUserArg(String arg) { - int userId; - if ("all".equals(arg)) { - userId = UserHandle.USER_ALL; - } else if ("current".equals(arg) || "cur".equals(arg)) { - userId = UserHandle.USER_CURRENT; - } else { - try { - userId = Integer.parseInt(arg); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Bad user number: " + arg); - } - } - return userId; - } - @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9896ec5e49a7..e28d198cf821 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1418,19 +1418,9 @@ final class ActivityStack { if (top == null) { return; } - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "ensureActivitiesVisible behind " + top + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top + " configChanges=0x" + Integer.toHexString(configChanges)); - - if (mTranslucentActivityWaiting != top) { - mUndrawnActivitiesBelowTopTranslucent.clear(); - if (mTranslucentActivityWaiting != null) { - // Call the callback with a timeout indication. - notifyActivityDrawnLocked(null); - mTranslucentActivityWaiting = null; - } - mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); - } + checkTranslucentActivityWaiting(top); // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. @@ -1458,7 +1448,6 @@ final class ActivityStack { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r + " finishing=" + r.finishing + " state=" + r.state); - // First: if this is not the current activity being started, make // sure it matches the current configuration. if (r != starting) { @@ -1466,143 +1455,28 @@ final class ActivityStack { } if (r.app == null || r.app.thread == null) { - // We need to make sure the app is running if it's the top, or it is - // just made visible from invisible. - // If the app is already visible, it must have died while it was visible. - // In this case, we'll show the dead window but will not restart the app. - // Otherwise we could end up thrashing. - if (r == top || !r.visible) { - // This activity needs to be visible, but isn't even running... - // get it started and resume if no other stack in this stack is resumed. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Start and freeze screen for " + r); - if (r != starting) { - r.startFreezingScreenLocked(r.app, configChanges); - } - if (!r.visible || r.mLaunchTaskBehind) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Starting and making visible: " + r); - setVisible(r, true); - } - if (r != starting) { - mStackSupervisor.startSpecificActivityLocked( - r, noStackActivityResumed, false); - if (activityNdx >= activities.size()) { - // Record may be removed if its process needs to restart. - activityNdx = activities.size() - 1; - } else { - noStackActivityResumed = false; - } + if (makeVisibleAndRestartIfNeeded(starting, configChanges, top, + noStackActivityResumed, r)) { + if (activityNdx >= activities.size()) { + // Record may be removed if its process needs to restart. + activityNdx = activities.size() - 1; + } else { + noStackActivityResumed = false; } } } else if (r.visible) { - // If this activity is already visible, then there is nothing - // else to do here. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Skipping: already visible at " + r); - r.stopFreezingScreenLocked(false); - try { - if (r.returningOptions != null) { - r.app.thread.scheduleOnNewActivityOptions(r.appToken, - r.returningOptions); - } - } catch(RemoteException e) { - } - if (r.state == ActivityState.RESUMED) { + if (alreadyVisible(r)) { noStackActivityResumed = false; } } else { - // This activity is not currently visible, but is running. - // Tell it to become visible. - r.visible = true; - if (r.state != ActivityState.RESUMED && r != starting) { - // If this activity is paused, tell it - // to now show its window. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Making visible and scheduling visibility: " + r); - try { - if (mTranslucentActivityWaiting != null) { - r.updateOptionsLocked(r.returningOptions); - mUndrawnActivitiesBelowTopTranslucent.add(r); - } - setVisible(r, true); - r.sleeping = false; - r.app.pendingUiClean = true; - r.app.thread.scheduleWindowVisibility(r.appToken, true); - r.stopFreezingScreenLocked(false); - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making visibile: " - + r.intent.getComponent(), e); - } - } + becomeVisible(starting, r); } - // Aggregate current change flags. configChanges |= r.configChangeFlags; - - if (r.fullscreen) { - // At this point, nothing else needs to be shown in this task. - behindFullscreenActivity = true; - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) { - behindFullscreenActivity = true; - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - } + behindFullscreenActivity = updateBehindFullscreen(stackInvisible, + behindFullscreenActivity, task, r); } else { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Make invisible? " + r + " finishing=" + r.finishing - + " state=" + r.state + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - // Now for any activities that aren't visible to the user, make - // sure they no longer are keeping the screen frozen. - if (r.visible) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r); - try { - setVisible(r, false); - switch (r.state) { - case STOPPING: - case STOPPED: - if (r.app != null && r.app.thread != null) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r.appToken, false); - } - break; - - case INITIALIZING: - case RESUMED: - case PAUSING: - case PAUSED: - // This case created for transitioning activities from - // translucent to opaque {@link Activity#convertToOpaque}. - if (getVisibleBehindActivity() == r) { - releaseBackgroundResources(r); - } else { - if (!mStackSupervisor.mStoppingActivities.contains(r)) { - mStackSupervisor.mStoppingActivities.add(r); - } - mStackSupervisor.scheduleIdleLocked(); - } - break; - - default: - break; - } - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making hidden: " - + r.intent.getComponent(), e); - } - } else { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); - } + becomeInvisible(stackInvisible, behindFullscreenActivity, r); } } if (mStackId == FREEFORM_WORKSPACE_STACK_ID) { @@ -1620,6 +1494,147 @@ final class ActivityStack { } } + private void checkTranslucentActivityWaiting(ActivityRecord top) { + if (mTranslucentActivityWaiting != top) { + mUndrawnActivitiesBelowTopTranslucent.clear(); + if (mTranslucentActivityWaiting != null) { + // Call the callback with a timeout indication. + notifyActivityDrawnLocked(null); + mTranslucentActivityWaiting = null; + } + mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); + } + } + + private boolean makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges, + ActivityRecord top, boolean noStackActivityResumed, ActivityRecord r) { + // We need to make sure the app is running if it's the top, or it is just made visible from + // invisible. If the app is already visible, it must have died while it was visible. In this + // case, we'll show the dead window but will not restart the app. Otherwise we could end up + // thrashing. + if (r == top || !r.visible) { + // This activity needs to be visible, but isn't even running... + // get it started and resume if no other stack in this stack is resumed. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r); + if (r != starting) { + r.startFreezingScreenLocked(r.app, configChanges); + } + if (!r.visible || r.mLaunchTaskBehind) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r); + setVisible(r, true); + } + if (r != starting) { + mStackSupervisor.startSpecificActivityLocked(r, noStackActivityResumed, false); + return true; + } + } + return false; + } + + private void becomeInvisible(boolean stackInvisible, boolean behindFullscreenActivity, + ActivityRecord r) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing=" + + r.finishing + " state=" + r.state + " stackInvisible=" + stackInvisible + + " behindFullscreenActivity=" + behindFullscreenActivity); + if (!r.visible) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); + return; + } + // Now for any activities that aren't visible to the user, make sure they no longer are + // keeping the screen frozen. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r); + try { + setVisible(r, false); + switch (r.state) { + case STOPPING: + case STOPPED: + if (r.app != null && r.app.thread != null) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Scheduling invisibility: " + r); + r.app.thread.scheduleWindowVisibility(r.appToken, false); + } + break; + + case INITIALIZING: + case RESUMED: + case PAUSING: + case PAUSED: + // This case created for transitioning activities from + // translucent to opaque {@link Activity#convertToOpaque}. + if (getVisibleBehindActivity() == r) { + releaseBackgroundResources(r); + } else { + if (!mStackSupervisor.mStoppingActivities.contains(r)) { + mStackSupervisor.mStoppingActivities.add(r); + } + mStackSupervisor.scheduleIdleLocked(); + } + break; + + default: + break; + } + } catch (Exception e) { + // Just skip on any failure; we'll make it visible when it next restarts. + Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e); + } + } + + private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity, + TaskRecord task, ActivityRecord r) { + if (r.fullscreen) { + // At this point, nothing else needs to be shown in this task. + behindFullscreenActivity = true; + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r + + " stackInvisible=" + stackInvisible + + " behindFullscreenActivity=" + behindFullscreenActivity); + } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) { + behindFullscreenActivity = true; + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r + + " stackInvisible=" + stackInvisible + + " behindFullscreenActivity=" + behindFullscreenActivity); + } + return behindFullscreenActivity; + } + + private void becomeVisible(ActivityRecord starting, ActivityRecord r) { + // This activity is not currently visible, but is running. Tell it to become visible. + r.visible = true; + if (r.state != ActivityState.RESUMED && r != starting) { + // If this activity is paused, tell it to now show its window. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Making visible and scheduling visibility: " + r); + try { + if (mTranslucentActivityWaiting != null) { + r.updateOptionsLocked(r.returningOptions); + mUndrawnActivitiesBelowTopTranslucent.add(r); + } + setVisible(r, true); + r.sleeping = false; + r.app.pendingUiClean = true; + r.app.thread.scheduleWindowVisibility(r.appToken, true); + r.stopFreezingScreenLocked(false); + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e); + } + } + } + + private boolean alreadyVisible(ActivityRecord r) { + // If this activity is already visible, then there is nothing else to do here. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r); + r.stopFreezingScreenLocked(false); + try { + if (r.returningOptions != null) { + r.app.thread.scheduleOnNewActivityOptions(r.appToken, r.returningOptions); + } + } catch(RemoteException e) { + } + return r.state == ActivityState.RESUMED; + } + void convertActivityToTranslucent(ActivityRecord r) { mTranslucentActivityWaiting = r; mUndrawnActivitiesBelowTopTranslucent.clear(); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 13d3ee1d18bc..0ec4b18ff28a 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3354,11 +3354,10 @@ public final class ActivityStackSupervisor implements DisplayListener { // preserve the old window until the new one is drawn. This prevents having a gap // between the removal and addition, in which no window is visible. We also want the // entrance of the new window to be properly animated. - mWindowManager.setReplacingWindow(topActivity.appToken, true /* animate */); + mWindowManager.setReplacingWindow(topActivity.appToken); } - final ActivityStack stack = - moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, - "moveTaskToStack:" + reason); + final ActivityStack stack = moveTaskToStackUncheckedLocked( + task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason); // Make sure the task has the appropriate bounds/size for the stack it is in. if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) { diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 82e0eafe6bea..e2a0f8241479 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -84,6 +84,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.DeviceIdleController; +import com.android.server.LocalServices; import com.android.server.accounts.AccountManagerService; import com.android.server.content.SyncStorageEngine.AuthorityInfo; import com.android.server.content.SyncStorageEngine.EndPoint; @@ -207,6 +209,7 @@ public class SyncManager { volatile private boolean mDataConnectionIsConnected = false; volatile private boolean mStorageIsLow = false; volatile private boolean mDeviceIsIdle = false; + volatile private boolean mReportedSyncActive = false; private final NotificationManager mNotificationMgr; private AlarmManager mAlarmService = null; @@ -267,6 +270,12 @@ public class SyncManager { SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL, null /* any sync */); } else { + if (mLocalDeviceIdleController != null) { + if (!mReportedSyncActive) { + mReportedSyncActive = true; + mLocalDeviceIdleController.setSyncActive(true); + } + } sendCheckAlarmsMessage(); } } @@ -292,6 +301,7 @@ public class SyncManager { }; private final PowerManager mPowerManager; + DeviceIdleController.LocalService mLocalDeviceIdleController; // Use this as a random offset to seed all periodic syncs. private int mSyncRandomOffsetMillis; @@ -1470,6 +1480,7 @@ public class SyncManager { } pw.print("memory low: "); pw.println(mStorageIsLow); pw.print("device idle: "); pw.println(mDeviceIsIdle); + pw.print("reported active: "); pw.println(mReportedSyncActive); final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts(); @@ -2544,6 +2555,7 @@ public class SyncManager { if (isLoggable) { Log.v(TAG, "maybeStartNextSync: no data connection, skipping"); } + setSyncActive(false); return Long.MAX_VALUE; } @@ -2551,6 +2563,7 @@ public class SyncManager { if (isLoggable) { Log.v(TAG, "maybeStartNextSync: memory low, skipping"); } + setSyncActive(false); return Long.MAX_VALUE; } @@ -2558,6 +2571,7 @@ public class SyncManager { if (isLoggable) { Log.v(TAG, "maybeStartNextSync: device idle, skipping"); } + setSyncActive(false); return Long.MAX_VALUE; } @@ -2567,6 +2581,7 @@ public class SyncManager { if (isLoggable) { Log.v(TAG, "maybeStartNextSync: accounts not known, skipping"); } + setSyncActive(false); return Long.MAX_VALUE; } @@ -2772,9 +2787,25 @@ public class SyncManager { dispatchSyncOperation(candidate); } + setSyncActive(mActiveSyncContexts.size() > 0); + return nextReadyToRunTime; } + void setSyncActive(boolean active) { + if (mLocalDeviceIdleController == null) { + mLocalDeviceIdleController + = LocalServices.getService(DeviceIdleController.LocalService.class); + } + if (mLocalDeviceIdleController != null) { + if (mReportedSyncActive != active) { + mReportedSyncActive = active; + mLocalDeviceIdleController.setSyncActive(active); + } + } + + } + private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) { final long bytesTransferredCurrent = getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 2b535b9699f7..759199ce58e6 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -51,6 +51,8 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.app.IBatteryStats; +import com.android.server.DeviceIdleController; +import com.android.server.LocalServices; import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; @@ -127,6 +129,7 @@ public class JobSchedulerService extends com.android.server.SystemService IBatteryStats mBatteryStats; PowerManager mPowerManager; + DeviceIdleController.LocalService mLocalDeviceIdleController; /** * Set to true once we are allowed to run third party apps. @@ -139,6 +142,11 @@ public class JobSchedulerService extends com.android.server.SystemService boolean mDeviceIdleMode; /** + * What we last reported to DeviceIdleController about wheter we are active. + */ + boolean mReportedActive; + + /** * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we * still clean up. On reinstall the package will have a new uid. */ @@ -268,6 +276,7 @@ public class JobSchedulerService extends com.android.server.SystemService mPendingJobs.remove(cancelled); // Cancel if running. stopJobOnServiceContextLocked(cancelled); + reportActive(); } } @@ -299,12 +308,39 @@ public class JobSchedulerService extends com.android.server.SystemService } } else { // When coming out of idle, allow thing to start back up. + if (rocking) { + if (mLocalDeviceIdleController != null) { + if (!mReportedActive) { + mReportedActive = true; + mLocalDeviceIdleController.setJobsActive(true); + } + } + } mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); } } } } + void reportActive() { + boolean active = false; + if (mPendingJobs.size() <= 0) { + for (int i=0; i<mActiveServices.size(); i++) { + JobServiceContext jsc = mActiveServices.get(i); + if (!jsc.isAvailable()) { + active = true; + break; + } + } + } + if (mLocalDeviceIdleController != null) { + if (mReportedActive != active) { + mReportedActive = active; + mLocalDeviceIdleController.setJobsActive(active); + } + } + } + /** * Initializes the system service. * <p> @@ -354,6 +390,8 @@ public class JobSchedulerService extends com.android.server.SystemService mReadyToRock = true; mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); + mLocalDeviceIdleController + = LocalServices.getService(DeviceIdleController.LocalService.class); // Create the "runners". for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { mActiveServices.add( @@ -623,6 +661,7 @@ public class JobSchedulerService extends com.android.server.SystemService stopJobOnServiceContextLocked(job); } } + reportActive(); if (DEBUG) { final int queuedJobs = mPendingJobs.size(); if (queuedJobs == 0) { @@ -685,6 +724,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); } } + reportActive(); if (DEBUG) { Slog.d(TAG, "idle=" + idleCount + " connectivity=" + connectivityCount + " charging=" + chargingCount + " tot=" + @@ -766,6 +806,7 @@ public class JobSchedulerService extends com.android.server.SystemService it.remove(); } } + reportActive(); } } } @@ -948,6 +989,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println(); pw.print("mReadyToRock="); pw.println(mReadyToRock); pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode); + pw.print("mReportedActive="); pw.println(mReportedActive); } pw.println(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c1525144765b..02a620468afc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1414,18 +1414,18 @@ public class PackageManagerService extends IPackageManager.Stub { } } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, firstUsers); + packageName, extras, 0, null, null, firstUsers); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, updateUsers); + packageName, extras, 0, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, null, null, updateUsers); + packageName, extras, 0, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null, null, packageName, null, updateUsers); + null, null, 0, packageName, null, updateUsers); // treat asec-hosted packages like removable media on upgrade if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { @@ -1956,8 +1956,7 @@ public class PackageManagerService extends IPackageManager.Stub { mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); - sUserManager = new UserManagerService(context, this, - mInstallLock, mPackages); + sUserManager = new UserManagerService(context, this, mPackages); // Propagate permission configuration in to package manager. ArrayMap<String, SystemConfig.PermissionEntry> permConfig @@ -9371,8 +9370,8 @@ public class PackageManagerService extends IPackageManager.Stub { } }; - final void sendPackageBroadcast(final String action, final String pkg, - final Bundle extras, final String targetPkg, final IIntentReceiver finishedReceiver, + final void sendPackageBroadcast(final String action, final String pkg, final Bundle extras, + final int flags, final String targetPkg, final IIntentReceiver finishedReceiver, final int[] userIds) { mHandler.post(new Runnable() { @Override @@ -9402,7 +9401,7 @@ public class PackageManagerService extends IPackageManager.Stub { intent.putExtra(Intent.EXTRA_UID, uid); } intent.putExtra(Intent.EXTRA_USER_HANDLE, id); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags); if (DEBUG_BROADCASTS) { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); @@ -9594,7 +9593,7 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, new int[] {userId}); + packageName, extras, 0, null, null, new int[] {userId}); try { IActivityManager am = ActivityManagerNative.getDefault(); final boolean isSystem = @@ -12904,11 +12903,11 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REPLACING, true); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, null, null, null); + extras, 0, null, null, null); sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, null, null, null); + extras, 0, null, null, null); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, - null, packageName, null, null); + null, 0, packageName, null, null); } } // Force a gc here. @@ -12943,14 +12942,14 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers); if (removedPackage != null) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null, removedUsers); + extras, 0, null, null, removedUsers); if (fullRemove && !replacing) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, - extras, null, null, removedUsers); + extras, 0, null, null, removedUsers); } } if (removedAppId >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null, + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, 0, null, null, removedUsers); } } @@ -14650,7 +14649,12 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null, + // If this is not reporting a change of the overall package, then only send it + // to registered receivers. We don't want to launch a swath of apps for every + // little component state change. + final int flags = !componentNames.contains(packageName) + ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0; + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null, new int[] {UserHandle.getUserId(packageUid)}); } @@ -14838,20 +14842,23 @@ public class PackageManagerService extends IPackageManager.Stub { static class DumpState { public static final int DUMP_LIBS = 1 << 0; public static final int DUMP_FEATURES = 1 << 1; - public static final int DUMP_RESOLVERS = 1 << 2; - public static final int DUMP_PERMISSIONS = 1 << 3; - public static final int DUMP_PACKAGES = 1 << 4; - public static final int DUMP_SHARED_USERS = 1 << 5; - public static final int DUMP_MESSAGES = 1 << 6; - public static final int DUMP_PROVIDERS = 1 << 7; - public static final int DUMP_VERIFIERS = 1 << 8; - public static final int DUMP_PREFERRED = 1 << 9; - public static final int DUMP_PREFERRED_XML = 1 << 10; - public static final int DUMP_KEYSETS = 1 << 11; - public static final int DUMP_VERSION = 1 << 12; - public static final int DUMP_INSTALLS = 1 << 13; - public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14; - public static final int DUMP_DOMAIN_PREFERRED = 1 << 15; + public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2; + public static final int DUMP_SERVICE_RESOLVERS = 1 << 3; + public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4; + public static final int DUMP_CONTENT_RESOLVERS = 1 << 5; + public static final int DUMP_PERMISSIONS = 1 << 6; + public static final int DUMP_PACKAGES = 1 << 7; + public static final int DUMP_SHARED_USERS = 1 << 8; + public static final int DUMP_MESSAGES = 1 << 9; + public static final int DUMP_PROVIDERS = 1 << 10; + public static final int DUMP_VERIFIERS = 1 << 11; + public static final int DUMP_PREFERRED = 1 << 12; + public static final int DUMP_PREFERRED_XML = 1 << 13; + public static final int DUMP_KEYSETS = 1 << 14; + public static final int DUMP_VERSION = 1 << 15; + public static final int DUMP_INSTALLS = 1 << 16; + public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17; + public static final int DUMP_DOMAIN_PREFERRED = 1 << 18; public static final int OPTION_SHOW_FILTERS = 1 << 0; @@ -14950,9 +14957,9 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" -h: print this help"); pw.println(" cmd may be one of:"); pw.println(" l[ibraries]: list known shared libraries"); - pw.println(" f[ibraries]: list device features"); + pw.println(" f[eatures]: list device features"); pw.println(" k[eysets]: print known keysets"); - pw.println(" r[esolvers]: dump intent resolvers"); + pw.println(" r[esolvers] [activity|service|receiver|content]: dump intent resolvers"); pw.println(" perm[issions]: dump permissions"); pw.println(" permission [name ...]: dump declaration and use of given permission"); pw.println(" pref[erred]: print preferred package settings"); @@ -15019,7 +15026,29 @@ public class PackageManagerService extends IPackageManager.Stub { } else if ("f".equals(cmd) || "features".equals(cmd)) { dumpState.setDump(DumpState.DUMP_FEATURES); } else if ("r".equals(cmd) || "resolvers".equals(cmd)) { - dumpState.setDump(DumpState.DUMP_RESOLVERS); + if (opti >= args.length) { + dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS + | DumpState.DUMP_SERVICE_RESOLVERS + | DumpState.DUMP_RECEIVER_RESOLVERS + | DumpState.DUMP_CONTENT_RESOLVERS); + } else { + while (opti < args.length) { + String name = args[opti]; + if ("a".equals(name) || "activity".equals(name)) { + dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS); + } else if ("s".equals(name) || "service".equals(name)) { + dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS); + } else if ("r".equals(name) || "receiver".equals(name)) { + dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS); + } else if ("c".equals(name) || "content".equals(name)) { + dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS); + } else { + pw.println("Error: unknown resolver table type: " + name); + return; + } + opti++; + } + } } else if ("perm".equals(cmd) || "permissions".equals(cmd)) { dumpState.setDump(DumpState.DUMP_PERMISSIONS); } else if ("permission".equals(cmd)) { @@ -15186,22 +15215,28 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) { if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:" : "Activity Resolver Table:", " ", packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) { if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", " ", packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) { if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:" : "Service Resolver Table:", " ", packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) { if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:" : "Provider Resolver Table:", " ", packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { @@ -15648,7 +15683,7 @@ public class PackageManagerService extends IPackageManager.Stub { } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver, null); + sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null); } } @@ -16353,23 +16388,26 @@ public class PackageManagerService extends IPackageManager.Stub { } /** Called by UserManagerService */ - void cleanUpUserLILPw(UserManagerService userManager, int userHandle) { - mDirtyUsers.remove(userHandle); - mSettings.removeUserLPw(userHandle); - mPendingBroadcasts.remove(userHandle); - if (mInstaller != null) { - // Technically, we shouldn't be doing this with the package lock - // held. However, this is very rare, and there is already so much - // other disk I/O going on, that we'll let it slide for now. - final StorageManager storage = mContext.getSystemService(StorageManager.class); - for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { - final String volumeUuid = vol.getFsUuid(); - if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid); - mInstaller.removeUserDataDirs(volumeUuid, userHandle); + void cleanUpUser(UserManagerService userManager, int userHandle) { + synchronized (mPackages) { + mDirtyUsers.remove(userHandle); + mUserNeedsBadging.delete(userHandle); + mSettings.removeUserLPw(userHandle); + mPendingBroadcasts.remove(userHandle); + } + synchronized (mInstallLock) { + if (mInstaller != null) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { + final String volumeUuid = vol.getFsUuid(); + if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid); + mInstaller.removeUserDataDirs(volumeUuid, userHandle); + } + } + synchronized (mPackages) { + removeUnusedPackagesLILPw(userManager, userHandle); } } - mUserNeedsBadging.delete(userHandle); - removeUnusedPackagesLILPw(userManager, userHandle); } /** @@ -16419,12 +16457,18 @@ public class PackageManagerService extends IPackageManager.Stub { } /** Called by UserManagerService */ - void createNewUserLILPw(int userHandle) { + void createNewUser(int userHandle) { if (mInstaller != null) { - mInstaller.createUserConfig(userHandle); - mSettings.createNewUserLILPw(this, mInstaller, userHandle); - applyFactoryDefaultBrowserLPw(userHandle); - primeDomainVerificationsLPw(userHandle); + synchronized (mInstallLock) { + synchronized (mPackages) { + mInstaller.createUserConfig(userHandle); + mSettings.createNewUserLILPw(this, mInstaller, userHandle); + } + } + synchronized (mPackages) { + applyFactoryDefaultBrowserLPw(userHandle); + primeDomainVerificationsLPw(userHandle); + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index d7176fd0776b..2cedc9ccd7c1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -35,6 +35,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; @@ -46,6 +47,7 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.text.TextUtils; +import android.util.PrintWriterPrinter; import com.android.internal.util.SizedInputStream; import libcore.io.IoUtils; @@ -56,6 +58,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -68,6 +71,7 @@ class PackageManagerShellCommand extends ShellCommand { final IPackageManager mInterface; final private WeakHashMap<String, Resources> mResourceCache = new WeakHashMap<String, Resources>(); + int mTargetUser; PackageManagerShellCommand(PackageManagerService service) { mInterface = service; @@ -97,6 +101,12 @@ class PackageManagerShellCommand extends ShellCommand { return runList(); case "uninstall": return runUninstall(); + case "query-intent-activities": + return runQueryIntentActivities(); + case "query-intent-services": + return runQueryIntentServices(); + case "query-intent-receivers": + return runQueryIntentReceivers(); default: return handleDefaultCommands(cmd); } @@ -340,7 +350,7 @@ class PackageManagerShellCommand extends ShellCommand { listThirdParty = true; break; case "--user": - userId = Integer.parseInt(getNextArg()); + userId = UserHandle.parseUserArg(getNextArgRequired()); break; default: pw.println("Error: Unknown option: " + opt); @@ -487,7 +497,7 @@ class PackageManagerShellCommand extends ShellCommand { flags |= PackageManager.DELETE_KEEP_DATA; break; case "--user": - userId = Integer.parseInt(getNextArg()); + userId = UserHandle.parseUserArg(getNextArgRequired()); break; default: pw.println("Error: Unknown option: " + opt); @@ -538,6 +548,104 @@ class PackageManagerShellCommand extends ShellCommand { } } + private Intent parseIntentAndUser() throws URISyntaxException { + mTargetUser = UserHandle.USER_CURRENT; + Intent intent = Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() { + @Override + public boolean handleOption(String opt, ShellCommand cmd) { + if ("--user".equals(opt)) { + mTargetUser = UserHandle.parseUserArg(cmd.getNextArgRequired()); + return true; + } + return false; + } + }); + mTargetUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), mTargetUser, false, false, null, null); + return intent; + } + + private int runQueryIntentActivities() { + Intent intent; + try { + intent = parseIntentAndUser(); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + List<ResolveInfo> result = mInterface.queryIntentActivities(intent, null, 0, + mTargetUser); + PrintWriter pw = getOutPrintWriter(); + if (result == null || result.size() <= 0) { + pw.println("No activities found"); + } else { + pw.print(result.size()); pw.println(" activities found:"); + PrintWriterPrinter pr = new PrintWriterPrinter(pw); + for (int i=0; i<result.size(); i++) { + pw.print(" Activity #"); pw.print(i); pw.println(":"); + result.get(i).dump(pr, " "); + } + } + } catch (RemoteException e) { + throw new RuntimeException("Failed calling service", e); + } + return 0; + } + + private int runQueryIntentServices() { + Intent intent; + try { + intent = parseIntentAndUser(); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + List<ResolveInfo> result = mInterface.queryIntentServices(intent, null, 0, + mTargetUser); + PrintWriter pw = getOutPrintWriter(); + if (result == null || result.size() <= 0) { + pw.println("No services found"); + } else { + pw.print(result.size()); pw.println(" services found:"); + PrintWriterPrinter pr = new PrintWriterPrinter(pw); + for (int i=0; i<result.size(); i++) { + pw.print(" Service #"); pw.print(i); pw.println(":"); + result.get(i).dump(pr, " "); + } + } + } catch (RemoteException e) { + throw new RuntimeException("Failed calling service", e); + } + return 0; + } + + private int runQueryIntentReceivers() { + Intent intent; + try { + intent = parseIntentAndUser(); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, null, 0, + mTargetUser); + PrintWriter pw = getOutPrintWriter(); + if (result == null || result.size() <= 0) { + pw.println("No receivers found"); + } else { + pw.print(result.size()); pw.println(" receivers found:"); + PrintWriterPrinter pr = new PrintWriterPrinter(pw); + for (int i=0; i<result.size(); i++) { + pw.print(" Receiver #"); pw.print(i); pw.println(":"); + result.get(i).dump(pr, " "); + } + } + } catch (RemoteException e) { + throw new RuntimeException("Failed calling service", e); + } + return 0; + } + private static class InstallParams { SessionParams sessionParams; String installerPackageName; @@ -598,7 +706,7 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.abiOverride = checkAbiArgument(getNextArg()); break; case "--user": - params.userId = Integer.parseInt(getNextArg()); + params.userId = UserHandle.parseUserArg(getNextArgRequired()); break; case "--install-location": sessionParams.installLocation = Integer.parseInt(getNextArg()); @@ -908,7 +1016,14 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -s: short summary"); pw.println(" -d: only list dangerous permissions"); pw.println(" -u: list only the permissions users will see"); - pw.println(""); + pw.println(" query-intent-activities [--user USER_ID] INTENT"); + pw.println(" Prints all activities that can handle the given Intent."); + pw.println(" query-intent-services [--user USER_ID] INTENT"); + pw.println(" Prints all services that can handle the given Intent."); + pw.println(" query-intent-receivers [--user USER_ID] INTENT"); + pw.println(" Prints all broadcast receivers that can handle the given Intent."); + pw.println(); + Intent.printIntentArgsHelp(pw , ""); } private static class LocalIntentReceiver { diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 5d8b1d281d54..903d12bcc217 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -103,6 +103,9 @@ public final class SELinuxMMAC { // Append privapp to existing seinfo label private static final String PRIVILEGED_APP_STR = ":privapp"; + // Append autoplay to existing seinfo label + private static final String AUTOPLAY_APP_STR = ":autoplayapp"; + /** * Load the mac_permissions.xml file containing all seinfo assignments used to * label apps. The loaded mac_permissions.xml file is determined by the @@ -316,6 +319,9 @@ public final class SELinuxMMAC { } } + if (pkg.applicationInfo.isAutoPlayApp()) + pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR; + if (pkg.applicationInfo.isPrivilegedApp()) pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index de1473943203..1d299d74b2db 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3861,7 +3861,7 @@ final class Settings { if (pkgSetting.getNotLaunched(userId)) { if (pkgSetting.installerPackageName != null) { yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, + pkgSetting.name, null, 0, pkgSetting.installerPackageName, null, new int[] {userId}); } pkgSetting.setNotLaunched(false, userId); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 3a1d2de0d37c..ab0b182762e1 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -97,8 +97,7 @@ import libcore.io.IoUtils; * * Method naming convention: * <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 "LP" should be called within the {@link #mPackagesLock} lock. * <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> @@ -164,7 +163,6 @@ public class UserManagerService extends IUserManager.Stub { private final Context mContext; 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(); @@ -215,6 +213,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mRestrictionsLock") private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>(); + @GuardedBy("mGuestRestrictions") private final Bundle mGuestRestrictions = new Bundle(); /** @@ -226,6 +225,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mUsersLock") private int[] mUserIds; + @GuardedBy("mPackagesLock") private int mNextSerialNumber; private int mUserVersion = 0; @@ -245,11 +245,9 @@ public class UserManagerService extends IUserManager.Stub { } } - /** - * Available for testing purposes. - */ - UserManagerService(File dataDir, File baseUserPath) { - this(null, null, new Object(), new Object(), dataDir, baseUserPath); + @VisibleForTesting + UserManagerService(File dataDir) { + this(null, null, new Object(), dataDir); } /** @@ -257,68 +255,53 @@ public class UserManagerService extends IUserManager.Stub { * associated with the package manager, and the given lock is the * package manager's own lock. */ - UserManagerService(Context context, PackageManagerService pm, - Object installLock, Object packagesLock) { - this(context, pm, installLock, packagesLock, - Environment.getDataDirectory(), - new File(Environment.getDataDirectory(), "user")); + UserManagerService(Context context, PackageManagerService pm, Object packagesLock) { + this(context, pm, packagesLock, Environment.getDataDirectory()); } - /** - * Available for testing purposes. - */ private UserManagerService(Context context, PackageManagerService pm, - Object installLock, Object packagesLock, - File dataDir, File baseUserPath) { + Object packagesLock, File dataDir) { mContext = context; mPm = pm; - mInstallLock = installLock; mPackagesLock = packagesLock; mHandler = new MainHandler(); - synchronized (mInstallLock) { - synchronized (mPackagesLock) { - mUsersDir = new File(dataDir, USER_INFO_DIR); - mUsersDir.mkdirs(); - // Make zeroth user directory, for services to migrate their files to that location - File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM)); - userZeroDir.mkdirs(); - FileUtils.setPermissions(mUsersDir.toString(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG - |FileUtils.S_IROTH|FileUtils.S_IXOTH, - -1, -1); - mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); - initDefaultGuestRestrictions(); - readUserListLILP(); - sInstance = this; - } + synchronized (mPackagesLock) { + mUsersDir = new File(dataDir, USER_INFO_DIR); + mUsersDir.mkdirs(); + // Make zeroth user directory, for services to migrate their files to that location + File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM)); + userZeroDir.mkdirs(); + FileUtils.setPermissions(mUsersDir.toString(), + FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, + -1, -1); + mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + initDefaultGuestRestrictions(); + readUserListLP(); + sInstance = this; } mLocalService = new LocalService(); LocalServices.addService(UserManagerInternal.class, mLocalService); } void systemReady() { - synchronized (mInstallLock) { - synchronized (mPackagesLock) { - 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); - } + // Prune out any partially created/partially removed users. + ArrayList<UserInfo> partials = new ArrayList<>(); + synchronized (mUsersLock) { + 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 + ")"); + removeUserState(ui.id); + } onUserForeground(UserHandle.USER_SYSTEM); mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -627,17 +610,22 @@ public class UserManagerService extends IUserManager.Stub { public void makeInitialized(int userId) { checkManageUsersPermission("makeInitialized"); - synchronized (mPackagesLock) { - UserInfo info = getUserInfoNoChecks(userId); + boolean scheduleWriteUser = false; + UserInfo info; + synchronized (mUsersLock) { + info = mUsers.get(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 + return; } - if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) { + if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) { info.flags |= UserInfo.FLAG_INITIALIZED; - scheduleWriteUser(info); + scheduleWriteUser = true; } } + if (scheduleWriteUser) { + scheduleWriteUser(info); + } } /** @@ -645,17 +633,18 @@ public class UserManagerService extends IUserManager.Stub { * restrictions. */ private void initDefaultGuestRestrictions() { - if (mGuestRestrictions.isEmpty()) { - mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true); - mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); + synchronized (mGuestRestrictions) { + if (mGuestRestrictions.isEmpty()) { + mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true); + mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); + } } } @Override public Bundle getDefaultGuestRestrictions() { checkManageUsersPermission("getDefaultGuestRestrictions"); - // TODO Switch to mGuestRestrictions for locking - synchronized (mPackagesLock) { + synchronized (mGuestRestrictions) { return new Bundle(mGuestRestrictions); } } @@ -663,12 +652,12 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setDefaultGuestRestrictions(Bundle restrictions) { checkManageUsersPermission("setDefaultGuestRestrictions"); - synchronized (mInstallLock) { - synchronized (mPackagesLock) { - mGuestRestrictions.clear(); - mGuestRestrictions.putAll(restrictions); - writeUserListLILP(); - } + synchronized (mGuestRestrictions) { + mGuestRestrictions.clear(); + mGuestRestrictions.putAll(restrictions); + } + synchronized (mPackagesLock) { + writeUserListLP(); } } @@ -775,7 +764,7 @@ public class UserManagerService extends IUserManager.Stub { Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId) != newRestrictions); mBaseUserRestrictions.put(userId, newRestrictions); - scheduleWriteUser(mUsers.get(userId)); + scheduleWriteUser(getUserInfoNoChecks(userId)); } final Bundle effective = computeEffectiveUserRestrictionsLR(userId); @@ -996,9 +985,9 @@ public class UserManagerService extends IUserManager.Stub { } } - private void readUserListLILP() { + private void readUserListLP() { if (!mUserListFile.exists()) { - fallbackToSingleUserLILP(); + fallbackToSingleUserLP(); return; } FileInputStream fis = null; @@ -1015,7 +1004,7 @@ public class UserManagerService extends IUserManager.Stub { if (type != XmlPullParser.START_TAG) { Slog.e(LOG_TAG, "Unable to read user list"); - fallbackToSingleUserLILP(); + fallbackToSingleUserLP(); return; } @@ -1036,7 +1025,7 @@ 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 = readUserLILP(Integer.parseInt(id)); + UserInfo user = readUserLP(Integer.parseInt(id)); if (user != null) { synchronized (mUsersLock) { @@ -1051,8 +1040,10 @@ public class UserManagerService extends IUserManager.Stub { && type != XmlPullParser.END_TAG) { if (type == XmlPullParser.START_TAG) { if (parser.getName().equals(TAG_RESTRICTIONS)) { - UserRestrictionsUtils - .readRestrictions(parser, mGuestRestrictions); + synchronized (mGuestRestrictions) { + UserRestrictionsUtils + .readRestrictions(parser, mGuestRestrictions); + } } break; } @@ -1061,25 +1052,18 @@ public class UserManagerService extends IUserManager.Stub { } } updateUserIds(); - upgradeIfNecessaryLILP(); - } catch (IOException ioe) { - fallbackToSingleUserLILP(); - } catch (XmlPullParserException pe) { - fallbackToSingleUserLILP(); + upgradeIfNecessaryLP(); + } catch (IOException | XmlPullParserException e) { + fallbackToSingleUserLP(); } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - } - } + IoUtils.closeQuietly(fis); } } /** * Upgrade steps between versions, either for fixing bugs or changing the data format. */ - private void upgradeIfNecessaryLILP() { + private void upgradeIfNecessaryLP() { int userVersion = mUserVersion; if (userVersion < 1) { // Assign a proper name for the owner, if not initialized correctly before @@ -1132,11 +1116,11 @@ public class UserManagerService extends IUserManager.Stub { + USER_VERSION); } else { mUserVersion = userVersion; - writeUserListLILP(); + writeUserListLP(); } } - private void fallbackToSingleUserLILP() { + private void fallbackToSingleUserLP() { int flags = UserInfo.FLAG_INITIALIZED; // In split system user mode, the admin and primary flags are assigned to the first human // user. @@ -1161,7 +1145,7 @@ public class UserManagerService extends IUserManager.Stub { updateUserIds(); initDefaultGuestRestrictions(); - writeUserListLILP(); + writeUserListLP(); writeUserLP(system); } @@ -1247,8 +1231,7 @@ public class UserManagerService extends IUserManager.Stub { * <user id="2"></user> * </users> */ - private void writeUserListLILP() { - // TODO Investigate removing a dependency on mInstallLock + private void writeUserListLP() { FileOutputStream fos = null; AtomicFile userListFile = new AtomicFile(mUserListFile); try { @@ -1266,8 +1249,10 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion)); serializer.startTag(null, TAG_GUEST_RESTRICTIONS); - UserRestrictionsUtils - .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS); + synchronized (mGuestRestrictions) { + UserRestrictionsUtils + .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS); + } serializer.endTag(null, TAG_GUEST_RESTRICTIONS); int[] userIdsToWrite; synchronized (mUsersLock) { @@ -1293,7 +1278,7 @@ public class UserManagerService extends IUserManager.Stub { } } - private UserInfo readUserLILP(int id) { + private UserInfo readUserLP(int id) { int flags = 0; int serialNumber = id; String name = null; @@ -1468,8 +1453,7 @@ public class UserManagerService extends IUserManager.Stub { } private UserInfo createUserInternal(String name, int flags, int parentId) { - if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean( - UserManager.DISALLOW_ADD_USER, false)) { + if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) { Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled."); return null; } @@ -1480,120 +1464,114 @@ public class UserManagerService extends IUserManager.Stub { final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0; final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0; final long ident = Binder.clearCallingIdentity(); - UserInfo userInfo = null; + UserInfo userInfo; final int userId; try { - synchronized (mInstallLock) { - synchronized (mPackagesLock) { - UserInfo parent = null; - if (parentId != UserHandle.USER_NULL) { - synchronized (mUsersLock) { - parent = getUserInfoLU(parentId); - } - if (parent == null) return null; - } - if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) { - Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId); - return null; - } - 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; + synchronized (mPackagesLock) { + UserInfo parent = null; + if (parentId != UserHandle.USER_NULL) { + synchronized (mUsersLock) { + parent = getUserInfoLU(parentId); } - // If we're adding a guest and there already exists one, bail. - if (isGuest && findCurrentGuestUser() != null) { + if (parent == null) return null; + } + if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) { + Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId); + return null; + } + 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 && findCurrentGuestUser() != null) { + return null; + } + // In legacy mode, restricted profile's parent can only be the owner user + if (isRestricted && !UserManager.isSplitSystemUser() + && (parentId != UserHandle.USER_SYSTEM)) { + Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner"); + return null; + } + if (isRestricted && UserManager.isSplitSystemUser()) { + if (parent == null) { + Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be " + + "specified"); return null; } - // In legacy mode, restricted profile's parent can only be the owner user - if (isRestricted && !UserManager.isSplitSystemUser() - && (parentId != UserHandle.USER_SYSTEM)) { - Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner"); + if (!parent.canHaveProfile()) { + Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be " + + "created for the specified parent user id " + parentId); return null; } - if (isRestricted && UserManager.isSplitSystemUser()) { - if (parent == null) { - Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be " - + "specified"); - return null; - } - if (!parent.canHaveProfile()) { - Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be " - + "created for the specified parent user id " + parentId); - return null; - } - } - // In split system user mode, we assign the first human user the primary flag. - // And if there is no device owner, we also assign the admin flag to primary - // user. - if (UserManager.isSplitSystemUser() - && !isGuest && !isManagedProfile && getPrimaryUser() == null) { - flags |= UserInfo.FLAG_PRIMARY; - DevicePolicyManager devicePolicyManager = (DevicePolicyManager) - mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - if (devicePolicyManager == null - || devicePolicyManager.getDeviceOwner() == null) { - flags |= UserInfo.FLAG_ADMIN; - } + } + // In split system user mode, we assign the first human user the primary flag. + // And if there is no device owner, we also assign the admin flag to primary user. + if (UserManager.isSplitSystemUser() + && !isGuest && !isManagedProfile && getPrimaryUser() == null) { + flags |= UserInfo.FLAG_PRIMARY; + DevicePolicyManager devicePolicyManager = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + if (devicePolicyManager == null + || devicePolicyManager.getDeviceOwner() == null) { + flags |= UserInfo.FLAG_ADMIN; } - userId = getNextAvailableId(); - userInfo = new UserInfo(userId, name, null, flags); - userInfo.serialNumber = mNextSerialNumber++; - long now = System.currentTimeMillis(); - userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0; - userInfo.partial = true; - Environment.getUserSystemDirectory(userInfo.id).mkdirs(); + } + userId = getNextAvailableId(); + userInfo = new UserInfo(userId, name, null, flags); + userInfo.serialNumber = mNextSerialNumber++; + long now = System.currentTimeMillis(); + userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0; + userInfo.partial = true; + Environment.getUserSystemDirectory(userInfo.id).mkdirs(); + synchronized (mUsersLock) { mUsers.put(userId, userInfo); - writeUserListLILP(); - if (parent != null) { - if (isManagedProfile) { - if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { - parent.profileGroupId = parent.id; - scheduleWriteUser(parent); - } - userInfo.profileGroupId = parent.profileGroupId; - } else if (isRestricted) { - if (!parent.canHaveProfile()) { - Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner"); - } - if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) { - parent.restrictedProfileParentId = parent.id; - scheduleWriteUser(parent); - } - userInfo.restrictedProfileParentId = parent.restrictedProfileParentId; + } + writeUserListLP(); + if (parent != null) { + if (isManagedProfile) { + if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { + parent.profileGroupId = parent.id; + writeUserLP(parent); } - } - - final StorageManager storage = mContext.getSystemService(StorageManager.class); - storage.createUserKey(userId, userInfo.serialNumber); - for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { - final String volumeUuid = vol.getFsUuid(); - try { - final File userDir = Environment.getDataUserDirectory(volumeUuid, - userId); - storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber); - enforceSerialNumber(userDir, userInfo.serialNumber); - } catch (IOException e) { - Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e); + userInfo.profileGroupId = parent.profileGroupId; + } else if (isRestricted) { + if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) { + parent.restrictedProfileParentId = parent.id; + writeUserLP(parent); } - } - mPm.createNewUserLILPw(userId); - userInfo.partial = false; - scheduleWriteUser(userInfo); - updateUserIds(); - Bundle restrictions = new Bundle(); - synchronized (mRestrictionsLock) { - mBaseUserRestrictions.append(userId, restrictions); + userInfo.restrictedProfileParentId = parent.restrictedProfileParentId; } } } - mPm.newUserCreated(userId); - if (userInfo != null) { - Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); - addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); - mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, - android.Manifest.permission.MANAGE_USERS); + final StorageManager storage = mContext.getSystemService(StorageManager.class); + storage.createUserKey(userId, userInfo.serialNumber); + for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { + final String volumeUuid = vol.getFsUuid(); + try { + final File userDir = Environment.getDataUserDirectory(volumeUuid, userId); + storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber); + enforceSerialNumber(userDir, userInfo.serialNumber); + } catch (IOException e) { + Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e); + } } + mPm.createNewUser(userId); + userInfo.partial = false; + synchronized (mPackagesLock) { + writeUserLP(userInfo); + } + updateUserIds(); + Bundle restrictions = new Bundle(); + synchronized (mRestrictionsLock) { + mBaseUserRestrictions.append(userId, restrictions); + } + mPm.newUserCreated(userId); + Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, + android.Manifest.permission.MANAGE_USERS); } finally { Binder.restoreCallingIdentity(ident); } @@ -1782,11 +1760,7 @@ public class UserManagerService extends IUserManager.Stub { // Clean up any ActivityManager state LocalServices.getService(ActivityManagerInternal.class) .onUserRemoved(userHandle); - synchronized (mInstallLock) { - synchronized (mPackagesLock) { - removeUserStateLILP(userHandle); - } - } + removeUserState(userHandle); } }.start(); } @@ -1798,10 +1772,10 @@ public class UserManagerService extends IUserManager.Stub { } } - private void removeUserStateLILP(final int userHandle) { + private void removeUserState(final int userHandle) { mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle); // Cleanup package manager settings - mPm.cleanUpUserLILPw(this, userHandle); + mPm.cleanUpUser(this, userHandle); // Remove this user from the list synchronized (mUsersLock) { @@ -1811,7 +1785,9 @@ public class UserManagerService extends IUserManager.Stub { AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); userFile.delete(); // Update the user list - writeUserListLILP(); + synchronized (mPackagesLock) { + writeUserListLP(); + } updateUserIds(); removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle)); } @@ -2146,17 +2122,15 @@ public class UserManagerService extends IUserManager.Stub { * @param userId the user that was just foregrounded */ public void onUserForeground(int userId) { - synchronized (mPackagesLock) { - UserInfo user = getUserInfoNoChecks(userId); - long now = System.currentTimeMillis(); - if (user == null || user.partial) { - Slog.w(LOG_TAG, "userForeground: unknown user #" + userId); - return; - } - if (now > EPOCH_PLUS_30_YEARS) { - user.lastLoggedInTime = now; - scheduleWriteUser(user); - } + UserInfo user = getUserInfoNoChecks(userId); + if (user == null || user.partial) { + Slog.w(LOG_TAG, "userForeground: unknown user #" + userId); + return; + } + long now = System.currentTimeMillis(); + if (now > EPOCH_PLUS_30_YEARS) { + user.lastLoggedInTime = now; + scheduleWriteUser(user); } } @@ -2164,7 +2138,6 @@ public class UserManagerService extends IUserManager.Stub { * Returns the next available user id, filling in any holes in the ids. * TODO: May not be a good idea to recycle ids, in case it results in confusion * for data and battery stats collection, or unexpected cross-talk. - * @return */ private int getNextAvailableId() { synchronized (mUsersLock) { @@ -2348,7 +2321,9 @@ public class UserManagerService extends IUserManager.Stub { } pw.println(); pw.println("Guest restrictions:"); - UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions); + synchronized (mGuestRestrictions) { + UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions); + } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index d713751d3786..c2466091bb91 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -409,6 +409,7 @@ final class AccessibilityController { private final Region mMagnifiedBounds = new Region(); private final Region mOldMagnifiedBounds = new Region(); + private final Region mOldAvailableBounds = new Region(); private final Path mCircularPath; @@ -537,29 +538,39 @@ final class AccessibilityController { screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset, Region.Op.INTERSECT); - if (!mOldMagnifiedBounds.equals(magnifiedBounds)) { - Region bounds = Region.obtain(); - bounds.set(magnifiedBounds); - mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, - bounds).sendToTarget(); - - mWindow.setBounds(magnifiedBounds); - Rect dirtyRect = mTempRect1; - if (mFullRedrawNeeded) { - mFullRedrawNeeded = false; - dirtyRect.set(mDrawBorderInset, mDrawBorderInset, - screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset); - mWindow.invalidate(dirtyRect); - } else { - Region dirtyRegion = mTempRegion3; - dirtyRegion.set(magnifiedBounds); - dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION); - dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT); - dirtyRegion.getBounds(dirtyRect); - mWindow.invalidate(dirtyRect); + final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds); + final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds); + if (magnifiedChanged || availableChanged) { + if (magnifiedChanged) { + mWindow.setBounds(magnifiedBounds); + Rect dirtyRect = mTempRect1; + if (mFullRedrawNeeded) { + mFullRedrawNeeded = false; + dirtyRect.set(mDrawBorderInset, mDrawBorderInset, + screenWidth - mDrawBorderInset, + screenHeight - mDrawBorderInset); + mWindow.invalidate(dirtyRect); + } else { + Region dirtyRegion = mTempRegion3; + dirtyRegion.set(magnifiedBounds); + dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION); + dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT); + dirtyRegion.getBounds(dirtyRect); + mWindow.invalidate(dirtyRect); + } + + mOldMagnifiedBounds.set(magnifiedBounds); + } + + if (availableChanged) { + mOldAvailableBounds.set(availableBounds); } - mOldMagnifiedBounds.set(magnifiedBounds); + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = Region.obtain(magnifiedBounds); + args.arg2 = Region.obtain(availableBounds); + mHandler.obtainMessage( + MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget(); } } @@ -867,9 +878,12 @@ final class AccessibilityController { public void handleMessage(Message message) { switch (message.what) { case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: { - Region bounds = (Region) message.obj; - mCallbacks.onMagnifedBoundsChanged(bounds); - bounds.recycle(); + final SomeArgs args = (SomeArgs) message.obj; + final Region magnifiedBounds = (Region) args.arg1; + final Region availableBounds = (Region) args.arg2; + mCallbacks.onMagnifiedBoundsChanged(magnifiedBounds, availableBounds); + magnifiedBounds.recycle(); + availableBounds.recycle(); } break; case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 89f565816a62..943c9ed39262 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -139,7 +139,7 @@ public class AppTransition implements Dump { private static final long APP_TRANSITION_TIMEOUT_MS = 5000; private final Context mContext; - private final Handler mH; + private final WindowManagerService mService; private int mNextAppTransition = TRANSIT_UNSET; @@ -208,15 +208,10 @@ public class AppTransition implements Dump { private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>(); private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor(); - private final Object mServiceLock; - private final WindowSurfacePlacer mWindowSurfacePlacer; - AppTransition(Context context, Handler h, Object serviceLock, - WindowSurfacePlacer windowSurfacePlacer) { + AppTransition(Context context, WindowManagerService service) { mContext = context; - mH = h; - mServiceLock = serviceLock; - mWindowSurfacePlacer = windowSurfacePlacer; + mService = service; mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, @@ -971,7 +966,7 @@ public class AppTransition implements Dump { @Override public void onAnimationEnd(Animation animation) { - mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget(); + mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget(); } @Override @@ -1326,7 +1321,8 @@ public class AppTransition implements Dump { void postAnimationCallback() { if (mNextAppTransitionCallback != null) { - mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback)); + mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, + mNextAppTransitionCallback)); mNextAppTransitionCallback = null; } } @@ -1478,14 +1474,15 @@ public class AppTransition implements Dump { } catch (RemoteException e) { Slog.w(TAG, "Failed to fetch app transition specs: " + e); } - synchronized (mServiceLock) { + synchronized (mService.mWindowMap) { mNextAppTransitionAnimationsSpecsPending = false; overridePendingAppTransitionMultiThumb(specs, mNextAppTransitionFutureCallback, null /* finishedCallback */, mNextAppTransitionScaleUp); mNextAppTransitionFutureCallback = null; - mWindowSurfacePlacer.requestTraversal(); + mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp); } + mService.requestTraversal(); } }); } @@ -1672,8 +1669,8 @@ public class AppTransition implements Dump { } boolean prepared = prepare(); if (isTransitionSet()) { - mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); + mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); } return prepared; } diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index 2905269eb21c..dfd01efe36d0 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -37,6 +37,10 @@ import java.util.ArrayList; public class AppWindowAnimator { static final String TAG = "AppWindowAnimator"; + private static final int PROLONG_ANIMATION_DISABLED = 0; + static final int PROLONG_ANIMATION_AT_END = 1; + static final int PROLONG_ANIMATION_AT_START = 2; + final AppWindowToken mAppToken; final WindowManagerService mService; final WindowAnimator mAnimator; @@ -85,7 +89,7 @@ public class AppWindowAnimator { // If true when the animation hits the last frame, it will keep running on that last frame. // This is used to synchronize animation with Recents and we wait for Recents to tell us to // finish or for a new animation be set as fail-safe mechanism. - private boolean mProlongAnimation; + private int mProlongAnimation; // Whether the prolong animation can be removed when animation is set. The purpose of this is // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it // when new animation is set. @@ -142,7 +146,7 @@ public class AppWindowAnimator { anim.setBackgroundColor(0); } if (mClearProlongedAnimation) { - mProlongAnimation = false; + mProlongAnimation = PROLONG_ANIMATION_DISABLED; } else { mClearProlongedAnimation = true; } @@ -266,6 +270,10 @@ public class AppWindowAnimator { return false; } transformation.clear(); + if (mProlongAnimation == PROLONG_ANIMATION_AT_START) { + animation.setStartTime(currentTime); + currentTime += 1; + } boolean hasMoreFrames = animation.getTransformation(currentTime, transformation); if (!hasMoreFrames) { if (deferThumbnailDestruction && !deferFinalFrameCleanup) { @@ -278,7 +286,7 @@ public class AppWindowAnimator { "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames + ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation); deferFinalFrameCleanup = false; - if (mProlongAnimation) { + if (mProlongAnimation == PROLONG_ANIMATION_AT_END) { hasMoreFrames = true; } else { animation = null; @@ -434,13 +442,13 @@ public class AppWindowAnimator { } } - void startProlongAnimation() { - mProlongAnimation = true; + void startProlongAnimation(int prolongType) { + mProlongAnimation = prolongType; mClearProlongedAnimation = false; } void endProlongedAnimation() { - mProlongAnimation = false; + mProlongAnimation = PROLONG_ANIMATION_DISABLED; } // This is an animation that does nothing: it just immediately finishes diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 425ff9b93b1c..3f4eaace838e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -128,8 +128,6 @@ class AppWindowToken extends WindowToken { boolean mWillReplaceWindow; // If true, the replaced window was already requested to be removed. boolean mReplacingRemoveRequested; - // Whether the replacement of the window should trigger app transition animation. - boolean mAnimateReplacingWindow; // If not null, the window that will be used to replace the old one. This is being set when // the window is added and unset when this window reports its first draw. WindowState mReplacingWindow; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e264c437241c..328c0436e2a0 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -22,7 +22,6 @@ 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; @@ -55,17 +54,6 @@ class DisplayContent { * from mDisplayWindows; */ private final WindowList mWindows = new WindowList(); - // This protects the following display size properties, so that - // getDisplaySize() doesn't need to acquire the global lock. This is - // needed because the window manager sometimes needs to use ActivityThread - // while it has its global state locked (for example to load animation - // resources), but the ActivityThread also needs get the current display - // size sometimes when it has its package lock held. - // - // These will only be modified with both mWindowMap and mDisplaySizeLock - // held (in that order) so the window manager doesn't need to acquire this - // lock when needing these values in its normal operation. - final Object mDisplaySizeLock = new Object(); int mInitialDisplayWidth = 0; int mInitialDisplayHeight = 0; int mInitialDisplayDensity = 0; @@ -202,18 +190,16 @@ class DisplayContent { } void initializeDisplayBaseInfo() { - synchronized(mDisplaySizeLock) { - // Bootstrap the default logical display from the display manager. - final DisplayInfo newDisplayInfo = - mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId); - if (newDisplayInfo != null) { - mDisplayInfo.copyFrom(newDisplayInfo); - } - mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth; - mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight; - mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; - mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight); + // Bootstrap the default logical display from the display manager. + final DisplayInfo newDisplayInfo = + mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId); + if (newDisplayInfo != null) { + mDisplayInfo.copyFrom(newDisplayInfo); } + mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth; + mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight; + mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; + mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight); } void getLogicalDisplayRect(Rect out) { diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 2be7ab8516b6..4926352829a0 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; import com.android.server.wm.WindowManagerService.DragInputEventReceiver; diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 3c3123ff0ba3..1f351cb94225 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -171,10 +171,10 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle, final WindowState child, int flags, final int type, final boolean isVisible, - final boolean hasFocus, final boolean hasWallpaper, DisplayContent displayContent) { + final boolean hasFocus, final boolean hasWallpaper) { // Add a window to our list of input windows. inputWindowHandle.name = child.toString(); - flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags, this); + flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags); inputWindowHandle.layoutParamsFlags = flags; inputWindowHandle.layoutParamsType = type; inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); @@ -308,8 +308,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { mService.mDragState.sendDragStartedIfNeededLw(child); } - addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus, - hasWallpaper, displayContent); + addInputWindowHandleLw( + inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 92eacd6b750f..a9bd71ff703b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -50,10 +50,12 @@ import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH; +import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; +import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; import android.Manifest; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.app.ActivityManagerNative; import android.app.AppOpsManager; import android.app.IActivityManager; @@ -910,7 +912,7 @@ public class WindowManagerService extends IWindowManager.Stub PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN"); mScreenFrozenLock.setReferenceCounted(false); - mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked); + mAppTransition = new AppTransition(context, this); mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier); mActivityManager = ActivityManagerNative.getDefault(); @@ -2077,7 +2079,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void prepareWindowReplacementTransition(AppWindowToken atoken) { - if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) { + if (atoken == null || !atoken.mWillReplaceWindow) { return; } atoken.allDrawn = false; @@ -3693,22 +3695,26 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback, onAnimationFinishedCallback, scaleUp); - if (!scaleUp) { - // This is used by freeform to recents windows transition. We need to synchronize - // the animation with the appearance of the content of recents, so we will make - // animation stay on the last frame a little longer. - mTmpTaskIds.clear(); - for (int i = specs.length - 1; i >= 0; i--) { - mTmpTaskIds.put(specs[i].taskId, 0); - } - for (final WindowState win : mWindowMap.values()) { - final Task task = win.getTask(); - if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) { - final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mAppAnimator != null) { - appToken.mAppAnimator.startProlongAnimation(); - } - } + prolongAnimationsFromSpecs(specs, scaleUp); + + } + } + + void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) { + // This is used by freeform <-> recents windows transition. We need to synchronize + // the animation with the appearance of the content of recents, so we will make + // animation stay on the first or last frame a little longer. + mTmpTaskIds.clear(); + for (int i = specs.length - 1; i >= 0; i--) { + mTmpTaskIds.put(specs[i].taskId, 0); + } + for (final WindowState win : mWindowMap.values()) { + final Task task = win.getTask(); + if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) { + final AppWindowToken appToken = win.mAppToken; + if (appToken != null && appToken.mAppAnimator != null) { + appToken.mAppAnimator.startProlongAnimation(scaleUp ? + PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END); } } } @@ -6908,27 +6914,25 @@ public class WindowManagerService extends IWindowManager.Stub final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation); final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - synchronized(displayContent.mDisplaySizeLock) { - displayInfo.rotation = mRotation; - displayInfo.logicalWidth = dw; - displayInfo.logicalHeight = dh; - displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; - displayInfo.appWidth = appWidth; - displayInfo.appHeight = appHeight; - displayInfo.getLogicalMetrics(mRealDisplayMetrics, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); - displayInfo.getAppMetrics(mDisplayMetrics); - if (displayContent.mDisplayScalingDisabled) { - displayInfo.flags |= Display.FLAG_SCALING_DISABLED; - } else { - displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; - } + displayInfo.rotation = mRotation; + displayInfo.logicalWidth = dw; + displayInfo.logicalHeight = dh; + displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; + displayInfo.appWidth = appWidth; + displayInfo.appHeight = appHeight; + displayInfo.getLogicalMetrics(mRealDisplayMetrics, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + displayInfo.getAppMetrics(mDisplayMetrics); + if (displayContent.mDisplayScalingDisabled) { + displayInfo.flags |= Display.FLAG_SCALING_DISABLED; + } else { + displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; + } - mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( - displayContent.getDisplayId(), displayInfo); + mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( + displayContent.getDisplayId(), displayInfo); - displayContent.mBaseDisplayRect.set(0, 0, dw, dh); - } + displayContent.mBaseDisplayRect.set(0, 0, dw, dh); if (false) { Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight); } @@ -8060,10 +8064,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { - synchronized(displayContent.mDisplaySizeLock) { - size.x = displayContent.mInitialDisplayWidth; - size.y = displayContent.mInitialDisplayHeight; - } + size.x = displayContent.mInitialDisplayWidth; + size.y = displayContent.mInitialDisplayHeight; } } } @@ -8073,10 +8075,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { - synchronized(displayContent.mDisplaySizeLock) { - size.x = displayContent.mBaseDisplayWidth; - size.y = displayContent.mBaseDisplayHeight; - } + size.x = displayContent.mBaseDisplayWidth; + size.y = displayContent.mBaseDisplayHeight; } } } @@ -8145,13 +8145,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, - int mode) { + private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) { Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off")); - - synchronized(displayContent.mDisplaySizeLock) { - displayContent.mDisplayScalingDisabled = (mode != 0); - } + displayContent.mDisplayScalingDisabled = (mode != 0); reconfigureDisplayLocked(displayContent); } @@ -8169,13 +8165,11 @@ public class WindowManagerService extends IWindowManager.Stub try { width = Integer.parseInt(sizeStr.substring(0, pos)); height = Integer.parseInt(sizeStr.substring(pos+1)); - synchronized(displayContent.mDisplaySizeLock) { - if (displayContent.mBaseDisplayWidth != width - || displayContent.mBaseDisplayHeight != height) { - Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height); - displayContent.mBaseDisplayWidth = width; - displayContent.mBaseDisplayHeight = height; - } + if (displayContent.mBaseDisplayWidth != width + || displayContent.mBaseDisplayHeight != height) { + Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height); + displayContent.mBaseDisplayWidth = width; + displayContent.mBaseDisplayHeight = height; } } catch (NumberFormatException ex) { } @@ -8192,11 +8186,9 @@ public class WindowManagerService extends IWindowManager.Stub int density; try { density = Integer.parseInt(densityStr); - synchronized(displayContent.mDisplaySizeLock) { - if (displayContent.mBaseDisplayDensity != density) { - Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density); - displayContent.mBaseDisplayDensity = density; - } + if (displayContent.mBaseDisplayDensity != density) { + Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density); + displayContent.mBaseDisplayDensity = density; } } catch (NumberFormatException ex) { } @@ -8206,21 +8198,16 @@ public class WindowManagerService extends IWindowManager.Stub int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DISPLAY_SCALING_FORCE, 0); if (mode != 0) { - synchronized(displayContent.mDisplaySizeLock) { - Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED"); - displayContent.mDisplayScalingDisabled = true; - } + Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED"); + displayContent.mDisplayScalingDisabled = true; } } // displayContent must not be null private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) { Slog.i(TAG, "Using new display size: " + width + "x" + height); - - synchronized(displayContent.mDisplaySizeLock) { - displayContent.mBaseDisplayWidth = width; - displayContent.mBaseDisplayHeight = height; - } + displayContent.mBaseDisplayWidth = width; + displayContent.mBaseDisplayHeight = height; reconfigureDisplayLocked(displayContent); } @@ -8256,9 +8243,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { - synchronized(displayContent.mDisplaySizeLock) { - return displayContent.mInitialDisplayDensity; - } + return displayContent.mInitialDisplayDensity; } } return -1; @@ -8269,9 +8254,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { - synchronized(displayContent.mDisplaySizeLock) { - return displayContent.mBaseDisplayDensity; - } + return displayContent.mBaseDisplayDensity; } } return -1; @@ -8306,10 +8289,7 @@ public class WindowManagerService extends IWindowManager.Stub // displayContent must not be null private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) { Slog.i(TAG, "Using new display density: " + density); - - synchronized(displayContent.mDisplaySizeLock) { - displayContent.mBaseDisplayDensity = density; - } + displayContent.mBaseDisplayDensity = density; reconfigureDisplayLocked(displayContent); } @@ -8400,12 +8380,10 @@ public class WindowManagerService extends IWindowManager.Stub private void setOverscanLocked(DisplayContent displayContent, int left, int top, int right, int bottom) { final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - synchronized (displayContent.mDisplaySizeLock) { - displayInfo.overscanLeft = left; - displayInfo.overscanTop = top; - displayInfo.overscanRight = right; - displayInfo.overscanBottom = bottom; - } + displayInfo.overscanLeft = left; + displayInfo.overscanTop = top; + displayInfo.overscanRight = right; + displayInfo.overscanBottom = bottom; mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top, right, bottom); @@ -8584,8 +8562,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (wtoken != null) { winAnimator.mAnimLayer = w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; - if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow && - wtoken.mReplacingWindow != w) { + if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) { // We know that we will be animating a relaunching window in the near future, // which will receive a z-order increase. We want the replaced window to // immediately receive the same treatment, e.g. to be above the dock divider. @@ -9992,14 +9969,11 @@ public class WindowManagerService extends IWindowManager.Stub DisplayInfo displayInfo = displayContent.getDisplayInfo(); final Rect rect = new Rect(); mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect); - synchronized (displayContent.mDisplaySizeLock) { - displayInfo.overscanLeft = rect.left; - displayInfo.overscanTop = rect.top; - displayInfo.overscanRight = rect.right; - displayInfo.overscanBottom = rect.bottom; - mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( - displayId, displayInfo); - } + displayInfo.overscanLeft = rect.left; + displayInfo.overscanTop = rect.top; + displayInfo.overscanRight = rect.right; + displayInfo.overscanBottom = rect.bottom; + mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId, displayInfo); configureDisplayPolicyLocked(displayContent); // TODO: Create an input channel for each display with touch capability. @@ -10125,9 +10099,8 @@ public class WindowManagerService extends IWindowManager.Stub * Hint to a token that its activity will relaunch, which will trigger removal and addition of * a window. * @param token Application token for which the activity will be relaunched. - * @param animate Whether to animate the addition of the new window. */ - public void setReplacingWindow(IBinder token, boolean animate) { + public void setReplacingWindow(IBinder token) { synchronized (mWindowMap) { AppWindowToken appWindowToken = findAppWindowToken(token); if (appWindowToken == null) { @@ -10138,7 +10111,13 @@ public class WindowManagerService extends IWindowManager.Stub + " as replacing window."); appWindowToken.mWillReplaceWindow = true; appWindowToken.mHasReplacedWindow = false; - appWindowToken.mAnimateReplacingWindow = animate; + + // Set-up dummy animation so we can start treating windows associated with this token + // like they are in transition before the new app window is ready for us to run the + // real transition animation. + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "setReplacingWindow() Setting dummy animation on: " + appWindowToken); + appWindowToken.mAppAnimator.setDummyAnimation(); } } @@ -10207,7 +10186,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setMagnificationCallbacks(MagnificationCallbacks callbacks) { + public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) { synchronized (mWindowMap) { if (mAccessibilityController == null) { mAccessibilityController = new AccessibilityController( diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 673c21ff9931..c6f7f4c343fb 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -16,66 +16,35 @@ package com.android.server.wm; -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; -import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; -import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; -import static android.view.WindowManager.LayoutParams.FLAG_SCALED; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; -import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; -import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; -import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; -import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; -import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; -import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT; -import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; -import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; -import static com.android.server.wm.WindowManagerService.DEBUG_POWER; -import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; -import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import com.android.server.input.InputWindowHandle; import android.app.ActivityManager; import android.app.AppOpsManager; -import android.graphics.Point; -import android.os.PowerManager; -import android.os.RemoteCallbackList; -import android.os.SystemClock; -import android.os.Trace; -import android.os.WorkSource; -import android.util.DisplayMetrics; -import android.util.TimeUtils; -import android.view.Display; -import android.view.IWindowFocusObserver; -import android.view.IWindowId; - -import com.android.server.input.InputWindowHandle; - import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; +import android.os.PowerManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; +import android.os.WorkSource; +import android.util.DisplayMetrics; import android.util.Slog; +import android.util.TimeUtils; +import android.view.Display; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IApplicationToken; import android.view.IWindow; +import android.view.IWindowFocusObserver; +import android.view.IWindowId; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; @@ -87,6 +56,40 @@ import android.view.WindowManagerPolicy; import java.io.PrintWriter; import java.util.ArrayList; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; +import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SCALED; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; +import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; +import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; +import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT; +import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; +import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; +import static com.android.server.wm.WindowManagerService.DEBUG_POWER; +import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; +import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; + class WindowList extends ArrayList<WindowState> { } @@ -106,8 +109,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { // to capture touch events in that area. static final int RESIZE_HANDLE_WIDTH_IN_DP = 30; - static final boolean BOUNDS_FOR_TOUCH = true; - static final int DRAG_RESIZE_MODE_FREEFORM = 0; static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1; @@ -1388,7 +1389,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { && token.mHasReplacedWindow) { if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this); token.mWillReplaceWindow = false; - token.mAnimateReplacingWindow = false; token.mReplacingRemoveRequested = false; token.mReplacingWindow = null; token.mHasReplacedWindow = false; @@ -1411,12 +1411,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace(); } - int getTouchableRegion(Region region, int flags, InputMonitor inputMonitor) { - final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0; + int getTouchableRegion(Region region, int flags) { + final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; if (modal && mAppToken != null) { // Limit the outer touch to the activity stack region. - flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + flags |= FLAG_NOT_TOUCH_MODAL; // If this is a modal window we need to dismiss it if it's not full screen and the // touch happens outside of the frame that displays the content. This means we // need to intercept touches outside of that window. The dim layer user @@ -1437,6 +1436,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mTmpRect.inset(-delta, -delta); } region.set(mTmpRect); + cropRegionToStackBoundsIfNeeded(region); } else { // Not modal or full screen modal getTouchableRegion(region); @@ -1772,26 +1772,41 @@ final class WindowState implements WindowManagerPolicy.WindowState { frame.right - inset.right, frame.bottom - inset.bottom); } - public void getTouchableRegion(Region outRegion) { + void getTouchableRegion(Region outRegion) { final Rect frame = mFrame; switch (mTouchableInsets) { default: - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + case TOUCHABLE_INSETS_FRAME: outRegion.set(frame); break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: + case TOUCHABLE_INSETS_CONTENT: applyInsets(outRegion, frame, mGivenContentInsets); break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: + case TOUCHABLE_INSETS_VISIBLE: applyInsets(outRegion, frame, mGivenVisibleInsets); break; - case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: { + case TOUCHABLE_INSETS_REGION: { final Region givenTouchableRegion = mGivenTouchableRegion; outRegion.set(givenTouchableRegion); outRegion.translate(frame.left, frame.top); break; } } + cropRegionToStackBoundsIfNeeded(outRegion); + } + + void cropRegionToStackBoundsIfNeeded(Region region) { + if (mAppToken == null || !mAppToken.mCropWindowsToStack) { + return; + } + + final TaskStack stack = getStack(); + if (stack == null) { + return; + } + + stack.getDimBounds(mTmpRect); + region.op(mTmpRect, Region.Op.INTERSECT); } WindowList getWindowList() { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index f7805b1ab9ec..6faf3a703431 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -44,7 +44,6 @@ import android.graphics.Region; import android.os.Debug; import android.os.RemoteException; import android.util.Slog; -import android.view.Display; import android.view.DisplayInfo; import android.view.MagnificationSpec; import android.view.Surface.OutOfResourcesException; @@ -124,16 +123,12 @@ class WindowStateAnimator { Rect mLastClipRect = new Rect(); Rect mTmpStackBounds = new Rect(); - // Used to save animation distances between the time they are calculated and when they are - // used. - int mAnimDw; - int mAnimDh; + // Used to save animation distances between the time they are calculated and when they are used. + private int mAnimDx; + private int mAnimDy; /** Is the next animation to be started a window move animation? */ - boolean mAnimateMove = false; - - /** Are we currently running a window move animation? */ - boolean mAnimatingMove = false; + private boolean mAnimateMove = false; float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; @@ -198,8 +193,8 @@ class WindowStateAnimator { final DisplayContent displayContent = win.getDisplayContent(); if (displayContent != null) { final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - mAnimDw = displayInfo.appWidth; - mAnimDh = displayInfo.appHeight; + mAnimDx = displayInfo.appWidth; + mAnimDy = displayInfo.appHeight; } else { Slog.w(TAG, "WindowStateAnimator ctor: Display has been removed"); // This is checked on return and dealt with. @@ -299,19 +294,19 @@ class WindowStateAnimator { TAG, "Starting animation in " + this + " @ " + currentTime + ": ww=" + mWin.mFrame.width() + " wh=" + mWin.mFrame.height() + - " dw=" + mAnimDw + " dh=" + mAnimDh + + " dx=" + mAnimDx + " dy=" + mAnimDy + " scale=" + mService.getWindowAnimationScaleLocked()); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (mAnimateMove) { mAnimateMove = false; mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), - mAnimDw, mAnimDh); + mAnimDx, mAnimDy); } else { mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), displayInfo.appWidth, displayInfo.appHeight); } - mAnimDw = displayInfo.appWidth; - mAnimDh = displayInfo.appHeight; + mAnimDx = displayInfo.appWidth; + mAnimDy = displayInfo.appHeight; mAnimation.setStartTime(mAnimationStartTime != -1 ? mAnimationStartTime : currentTime); @@ -368,7 +363,6 @@ class WindowStateAnimator { mAnimating = false; mKeyguardGoingAwayAnimation = false; - mAnimatingMove = false; mLocalAnimating = false; if (mAnimation != null) { mAnimation.cancel(); @@ -773,7 +767,14 @@ class WindowStateAnimator { mPendingDestroySurface = mSurfaceController; } } else { - WindowManagerService.logSurface(mWin, "DESTROY", null); + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + WindowManagerService.logSurface(mWin, "DESTROY", null); + } destroySurface(); } // Don't hide wallpaper if we're deferring the surface destroy @@ -1606,7 +1607,7 @@ class WindowStateAnimator { fadeOut.setDuration(fadeDuration); fadeOut.setStartOffset(elapsed); newAnimation.addAnimation(fadeOut); - newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh); + newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDx, mAnimDy); mAnimation = newAnimation; } @@ -1680,4 +1681,13 @@ class WindowStateAnimator { mSurfaceController.destroyInTransaction(); mSurfaceController = null; } + + void setMoveAnimation(int left, int top) { + final Animation a = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.window_move_from_decor); + setAnimation(a); + mAnimDx = mWin.mLastFrame.left - left; + mAnimDy = mWin.mLastFrame.top - top; + mAnimateMove = true; + } } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index ae96658e9264..979f55baa369 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -17,7 +17,6 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowManagerService.DEBUG; import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; -import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS; @@ -57,7 +56,6 @@ import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.view.animation.Animation; -import android.view.animation.AnimationUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -679,20 +677,17 @@ class WindowSurfacePlacer { final WindowStateAnimator winAnimator = w.mWinAnimator; // If the window has moved due to its containing content frame changing, then - // notify the listeners and optionally animate it. + // notify the listeners and optionally animate it. Simply checking a change of + // position is not enough, because being move due to dock divider is not a trigger + // for animation. if (w.hasMoved()) { // Frame has moved, containing content frame has also moved, and we're not // currently animating... let's do something. final int left = w.mFrame.left; final int top = w.mFrame.top; - if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) { - Animation a = AnimationUtils.loadAnimation(mService.mContext, - com.android.internal.R.anim.window_move_from_decor); - winAnimator.setAnimation(a); - winAnimator.mAnimDw = w.mLastFrame.left - left; - winAnimator.mAnimDh = w.mLastFrame.top - top; - winAnimator.mAnimateMove = true; - winAnimator.mAnimatingMove = true; + if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 + && !w.isDragResizing()) { + winAnimator.setMoveAnimation(left, top); } //TODO (multidisplay): Accessibility supported only for the default display. diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index f2a1878d0dc5..d292f623a33e 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -747,7 +747,13 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } } - std::vector<Attribute::Symbol> items; + struct SymbolComparator { + bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { + return a.symbol.name.value() < b.symbol.name.value(); + } + }; + + std::set<Attribute::Symbol, SymbolComparator> items; std::u16string comment; bool error = false; @@ -785,15 +791,27 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { + Attribute::Symbol& symbol = s.value(); ParsedResource childResource; - childResource.name = s.value().symbol.name.value(); + childResource.name = symbol.symbol.name.value(); childResource.source = itemSource; childResource.value = util::make_unique<Id>(); outResource->childResources.push_back(std::move(childResource)); - s.value().symbol.setComment(std::move(comment)); - s.value().symbol.setSource(itemSource); - items.push_back(std::move(s.value())); + symbol.symbol.setComment(std::move(comment)); + symbol.symbol.setSource(itemSource); + + auto insertResult = items.insert(std::move(symbol)); + if (!insertResult.second) { + const Attribute::Symbol& existingSymbol = *insertResult.first; + mDiag->error(DiagMessage(itemSource) + << "duplicate symbol '" << existingSymbol.symbol.name.value().entry + << "'"); + + mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) + << "first defined here"); + error = true; + } } else { error = true; } @@ -810,7 +828,7 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes } std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); - attr->symbols.swap(items); + attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); outResource->value = std::move(attr); return true; diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index d3c3c1044ae7..b1a4c7dee73f 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -23,22 +23,28 @@ namespace aapt { namespace ResourceUtils { -void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, +bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, StringPiece16* outType, StringPiece16* outEntry) { + bool hasPackageSeparator = false; + bool hasTypeSeparator = false; const char16_t* start = str.data(); const char16_t* end = start + str.size(); const char16_t* current = start; while (current != end) { if (outType->size() == 0 && *current == u'/') { + hasTypeSeparator = true; outType->assign(start, current - start); start = current + 1; } else if (outPackage->size() == 0 && *current == u':') { + hasPackageSeparator = true; outPackage->assign(start, current - start); start = current + 1; } current++; } outEntry->assign(start, end - start); + + return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); } bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, @@ -62,26 +68,34 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* StringPiece16 package; StringPiece16 type; StringPiece16 entry; - extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type, - &entry); + if (!extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), + &package, &type, &entry)) { + return false; + } const ResourceType* parsedType = parseResourceType(type); if (!parsedType) { return false; } + if (entry.empty()) { + return false; + } + if (create && *parsedType != ResourceType::kId) { return false; } - if (outRef != nullptr) { + if (outRef) { outRef->package = package; outRef->type = *parsedType; outRef->entry = entry; } + if (outCreate) { *outCreate = create; } + if (outPrivate) { *outPrivate = priv; } @@ -104,20 +118,33 @@ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRe StringPiece16 package; StringPiece16 type; StringPiece16 entry; - extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry); + if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), + &package, &type, &entry)) { + return false; + } if (!type.empty() && type != u"attr") { return false; } - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = ResourceType::kAttr; + outRef->entry = entry; + } return true; } return false; } +bool isAttributeReference(const StringPiece16& str) { + return tryParseAttributeReference(str, nullptr); +} + /* * Style parent's are a bit different. We accept the following formats: * diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 851edc89fa90..34daa66b1546 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -34,8 +34,9 @@ namespace ResourceUtils { * * where the package can be empty. Validation must be performed on each * individual extracted piece to verify that the pieces are valid. + * Returns false if there was no package but a ':' was present. */ -void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, +bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, StringPiece16* outType, StringPiece16* outEntry); /* @@ -54,12 +55,17 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, bool isReference(const StringPiece16& str); /* - * Returns true if the string was parsed as an attribute reference (?[package:]type/name), + * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), * with `outReference` set to the parsed reference. */ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference); /** + * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). + */ +bool isAttributeReference(const StringPiece16& str); + +/** * Returns true if the value is a boolean, putting the result in `outValue`. */ bool tryParseBool(const StringPiece16& str, bool* outValue); diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 7de8f4130316..3d2a6e18e2bc 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -90,6 +90,26 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { &privateRef)); } +TEST(ResourceUtilsTest, ParseAttributeReferences) { + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo")); +} + +TEST(ResourceUtilsTest, FailParseIncompleteReference) { + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo")); +} + TEST(ResourceUtilsTest, ParseStyleParentReference) { const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" }; const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" }; diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h index 9a46bcb9fc5c..721bf5bd6320 100644 --- a/tools/aapt2/XmlDom.h +++ b/tools/aapt2/XmlDom.h @@ -215,10 +215,13 @@ public: for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { if (name.package == iter->prefix) { if (iter->package.empty()) { - return ResourceName{ localPackage.toString(), name.type, name.entry }; - } else { + if (localPackage != name.package) { + return ResourceName{ localPackage.toString(), name.type, name.entry }; + } + } else if (iter->package != name.package) { return ResourceName{ iter->package, name.type, name.entry }; } + break; } } return {}; diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 93f2dc6f04cd..97be774acaf1 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -50,7 +50,7 @@ struct LinkOptions { std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; Maybe<std::string> generateJavaClassPath; - std::vector<std::string> extraJavaPackages; + std::set<std::string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; bool noAutoVersion = false; bool staticLib = false; @@ -485,7 +485,7 @@ public: } } - mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) }; + mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) }); return true; } @@ -640,8 +640,7 @@ public: } } - for (auto& pair : mFilesToProcess) { - FileToProcess& file = pair.second; + for (const FileToProcess& file : mFilesToProcess) { if (file.file.name.type != ResourceType::kRaw && util::stringEndsWith<char>(file.source.path, ".xml.flat")) { if (mOptions.verbose) { @@ -760,7 +759,7 @@ public: return 1; } - for (std::string& extraPackage : mOptions.extraJavaPackages) { + for (const std::string& extraPackage : mOptions.extraJavaPackages) { if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage), options)) { return 1; @@ -795,16 +794,24 @@ private: std::unique_ptr<TableMerger> mTableMerger; struct FileToProcess { - Source source; ResourceFile file; + Source source; + }; + + struct FileToProcessComparator { + bool operator()(const FileToProcess& a, const FileToProcess& b) { + return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config); + } }; - std::map<ResourceName, FileToProcess> mFilesToProcess; + + std::set<FileToProcess, FileToProcessComparator> mFilesToProcess; }; int link(const std::vector<StringPiece>& args) { LinkOptions options; Maybe<std::string> privateSymbolsPackage; Maybe<std::string> minSdkVersion, targetSdkVersion; + std::vector<std::string> extraJavaPackages; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", @@ -833,7 +840,7 @@ int link(const std::vector<StringPiece>& args) { "If not specified, public and private symbols will use the application's " "package name", &privateSymbolsPackage) .optionalFlagList("--extra-packages", "Generate the same R.java but with different " - "package names", &options.extraJavaPackages) + "package names", &extraJavaPackages) .optionalSwitch("-v", "Enables verbose logging", &options.verbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { @@ -852,6 +859,14 @@ int link(const std::vector<StringPiece>& args) { options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value()); } + // Populate the set of extra packages for which to generate R.java. + for (std::string& extraPackage : extraJavaPackages) { + // A given package can actually be a colon separated list of packages. + for (StringPiece package : util::split(extraPackage, ':')) { + options.extraJavaPackages.insert(package.toString()); + } + } + LinkCommand cmd(options); return cmd.run(flags.getArgs()); } diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h index c70531ba8296..a4cb596eb5ec 100644 --- a/tools/aapt2/link/ReferenceLinkerVisitor.h +++ b/tools/aapt2/link/ReferenceLinkerVisitor.h @@ -80,7 +80,7 @@ public: return; } - DiagMessage errorMsg; + DiagMessage errorMsg(reference->getSource()); errorMsg << "reference to " << reference->name.value(); if (realName) { errorMsg << " (aka " << realName.value() << ")"; @@ -92,7 +92,7 @@ public: } if (!mSymbols->findById(reference->id.value())) { - mContext->getDiagnostics()->error(DiagMessage() + mContext->getDiagnostics()->error(DiagMessage(reference->getSource()) << "reference to " << reference->id.value() << " was not found"); mError = true; diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 147b9bf16248..caab9b8a4684 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -33,6 +33,7 @@ class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor { private: IAaptContext* mContext; ISymbolTable* mSymbols; + Source mSource; std::set<int>* mSdkLevelsFound; ReferenceLinkerVisitor mReferenceLinkerVisitor; bool mError = false; @@ -40,13 +41,14 @@ private: public: using xml::PackageAwareVisitor::visit; - XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, + XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source, std::set<int>* sdkLevelsFound) : - mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound), + mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound), mReferenceLinkerVisitor(context, symbols, this) { } void visit(xml::Element* el) override { + const Source source = mSource.withLine(el->lineNumber); for (xml::Attribute& attr : el->attributes) { Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(attr.namespaceUri); @@ -76,15 +78,16 @@ public: !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { // We won't be able to encode this as a string. mContext->getDiagnostics()->error( - DiagMessage() << "'" << attr.value << "' " - << "is incompatible with attribute " - << package << ":" << attr.name << " " << *attribute); + DiagMessage(source) << "'" << attr.value << "' " + << "is incompatible with attribute " + << package << ":" << attr.name << " " + << *attribute); mError = true; } } else { - mContext->getDiagnostics()->error( - DiagMessage() << "attribute '" << package << ":" << attr.name - << "' was not found"); + mContext->getDiagnostics()->error(DiagMessage(source) + << "attribute '" << package << ":" + << attr.name << "' was not found"); mError = true; } @@ -95,6 +98,7 @@ public: if (attr.compiledValue) { // With a compiledValue, we must resolve the reference and assign it an ID. + attr.compiledValue->setSource(source); attr.compiledValue->accept(&mReferenceLinkerVisitor); } } @@ -123,7 +127,8 @@ public: bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) { mSdkLevelsFound.clear(); - XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound); + XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), resource->file.source, + &mSdkLevelsFound); if (resource->root) { resource->root->accept(&visitor); return !visitor.hasError(); diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 9a8b263f5f97..bb33ea78f496 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -77,16 +77,8 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n } -static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table, - ResourceId id) { - android::Res_value val = {}; - ssize_t block = table.getResource(id.id, &val, true); - if (block >= 0) { - std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>(); - s->id = id; - return s; - } - +static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table, + ResourceId id) { // Try as a bag. const android::ResTable::bag_entry* entry; ssize_t count = table.lockBag(id.id, &entry); @@ -148,14 +140,23 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa for (const auto& asset : mAssets) { const android::ResTable& table = asset->getResources(false); StringPiece16 typeStr = toString(name.type); + uint32_t typeSpecFlags = 0; ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(), typeStr.data(), typeStr.size(), - name.package.data(), name.package.size()); + name.package.data(), name.package.size(), + &typeSpecFlags); if (!resId.isValid()) { continue; } - std::shared_ptr<Symbol> s = lookupIdInTable(table, resId); + std::shared_ptr<Symbol> s; + if (name.type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, resId); + } else { + s = std::make_shared<Symbol>(); + s->id = resId; + } + if (s) { mCache.put(name, s); return s.get(); @@ -173,7 +174,28 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa for (const auto& asset : mAssets) { const android::ResTable& table = asset->getResources(false); - std::shared_ptr<Symbol> s = lookupIdInTable(table, id); + android::ResTable::resource_name name; + if (!table.getResourceName(id.id, true, &name)) { + continue; + } + + bool isAttr = false; + if (name.type) { + if (const ResourceType* t = parseResourceType(StringPiece16(name.type, name.typeLen))) { + isAttr = (*t == ResourceType::kAttr); + } + } else if (name.type8) { + isAttr = (StringPiece(name.type8, name.typeLen) == "attr"); + } + + std::shared_ptr<Symbol> s; + if (isAttr) { + s = lookupAttributeInTable(table, id); + } else { + s = std::make_shared<Symbol>(); + s->id = id; + } + if (s) { mIdCache.put(id, s); return s.get(); diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 0d17e8467d32..304833481be0 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -304,6 +304,11 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { return false; } + // There can be multiple packages in a table, so + // clear the type and key pool in case they were set from a previous package. + mTypePool.uninit(); + mKeyPool.uninit(); + ResChunkPullParser parser(getChunkData(&packageHeader->header), getChunkDataLen(&packageHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 80552a540ec3..324afb3c7de3 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -229,11 +229,12 @@ public: private: friend class Tokenizer<Char>; - iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok); + iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end); - BasicStringPiece<Char> str; - Char separator; - BasicStringPiece<Char> token; + BasicStringPiece<Char> mStr; + Char mSeparator; + BasicStringPiece<Char> mToken; + bool mEnd; }; Tokenizer(BasicStringPiece<Char> str, Char sep); @@ -252,36 +253,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) { template <typename Char> typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() { - const Char* start = token.end(); - const Char* end = str.end(); + const Char* start = mToken.end(); + const Char* end = mStr.end(); if (start == end) { - token.assign(token.end(), 0); + mEnd = true; + mToken.assign(mToken.end(), 0); return *this; } start += 1; const Char* current = start; while (current != end) { - if (*current == separator) { - token.assign(start, current - start); + if (*current == mSeparator) { + mToken.assign(start, current - start); return *this; } ++current; } - token.assign(start, end - start); + mToken.assign(start, end - start); return *this; } template <typename Char> inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() { - return token; + return mToken; } template <typename Char> inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const { // We check equality here a bit differently. // We need to know that the addresses are the same. - return token.begin() == rhs.token.begin() && token.end() == rhs.token.end(); + return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() && + mEnd == rhs.mEnd; } template <typename Char> @@ -291,8 +294,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const { template <typename Char> inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep, - BasicStringPiece<Char> tok) : - str(s), separator(sep), token(tok) { + BasicStringPiece<Char> tok, bool end) : + mStr(s), mSeparator(sep), mToken(tok), mEnd(end) { } template <typename Char> @@ -307,8 +310,8 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() { template <typename Char> inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : - mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))), - mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) { + mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)), + mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) { } inline uint16_t hostToDevice16(uint16_t value) { diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 9db9fb7f112a..9208e07e635b 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -101,6 +101,15 @@ TEST(UtilTest, TokenizeInput) { ASSERT_EQ(tokenizer.end(), iter); } +TEST(UtilTest, TokenizeEmptyString) { + auto tokenizer = util::tokenize(StringPiece16(u""), u'|'); + auto iter = tokenizer.begin(); + ASSERT_NE(tokenizer.end(), iter); + ASSERT_EQ(StringPiece16(), *iter); + ++iter; + ASSERT_EQ(tokenizer.end(), iter); +} + TEST(UtilTest, TokenizeAtEnd) { auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.'); auto iter = tokenizer.begin(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java index 868c6d328e8e..b67afebf9a89 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java @@ -16,10 +16,13 @@ package com.android.layoutlib.bridge.bars; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; @@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar { private Object mWindowDecorActionBar; private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; + // This is used on v23.1.1 and later. + private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar"; private Class<?> mWindowActionBarClass; /** @@ -70,14 +75,26 @@ public class AppCompatActionBar extends BridgeActionBar { try { Class[] constructorParams = {View.class}; Object[] constructorArgs = {getDecorContent()}; - mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS, - constructorParams, constructorArgs); + LayoutlibCallback callback = params.getLayoutlibCallback(); + // First try to load the class as was available before appcompat v23.1.1, without + // logging warnings. + try { + mWindowDecorActionBar = callback.loadClass(WINDOW_ACTION_BAR_CLASS, + constructorParams, constructorArgs); + } catch (ClassNotFoundException ignore) { + } + if (mWindowDecorActionBar == null) { + // If failed, load the new class, while logging warnings. + mWindowDecorActionBar = callback.loadView(WINDOW_ACTION_BAR_CLASS_NEW, + constructorParams, constructorArgs); + } mWindowActionBarClass = mWindowDecorActionBar == null ? null : mWindowDecorActionBar.getClass(); setupActionBar(); } catch (Exception e) { - e.printStackTrace(); + Bridge.getLog().warning(LayoutLog.TAG_BROKEN, + "Failed to load AppCompat ActionBar with unknown error.", e); } } diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index e611ea448497..6e423911f09c 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -737,7 +737,9 @@ public class WifiEnterpriseConfig implements Parcelable { public String toString() { StringBuffer sb = new StringBuffer(); for (String key : mFields.keySet()) { - sb.append(key).append(" ").append(mFields.get(key)).append("\n"); + // Don't display password in toString(). + String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key); + sb.append(key).append(" ").append(value).append("\n"); } return sb.toString(); } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index c26ca6ed2aff..5534cad16872 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -168,6 +168,22 @@ public class WifiScanner { * to wake up at fixed interval */ public int maxScansToCache; + /** + * if maxPeriodInMs is non zero or different than period, then this bucket is + * an exponential backoff bucket and the scan period will grow exponentially + * as per formula: actual_period(N) = period ^ (N/(step_count+1)) + * to a maximum period of max_period. + */ + public int maxPeriodInMs; + /** + * for exponential back off bucket: multiplier: new_period=old_period*exponent + */ + public int exponent; + /** + * for exponential back off bucket, number of scans performed at a given + * period and until the exponent is applied + */ + public int stepCount; /** Implement the Parcelable interface {@hide} */ public int describeContents() { @@ -181,6 +197,9 @@ public class WifiScanner { dest.writeInt(reportEvents); dest.writeInt(numBssidsPerScan); dest.writeInt(maxScansToCache); + dest.writeInt(maxPeriodInMs); + dest.writeInt(exponent); + dest.writeInt(stepCount); if (channels != null) { dest.writeInt(channels.length); @@ -206,6 +225,9 @@ public class WifiScanner { settings.reportEvents = in.readInt(); settings.numBssidsPerScan = in.readInt(); settings.maxScansToCache = in.readInt(); + settings.maxPeriodInMs = in.readInt(); + settings.exponent = in.readInt(); + settings.stepCount = in.readInt(); int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { |