Merge "Use correct rounding in View for potentially negative values"
diff --git a/api/current.txt b/api/current.txt
index 96ecad6..e7627f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -332,6 +332,7 @@
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 @@
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 @@
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 @@
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
@@ -5146,10 +5166,12 @@
}
public static class NotificationManager.Policy implements android.os.Parcelable {
- ctor public NotificationManager.Policy(int, int, int);
+ ctor public deprecated NotificationManager.Policy(int, int, int);
+ ctor public NotificationManager.Policy(int, int, int, int);
method public int describeContents();
method public static java.lang.String priorityCategoriesToString(int);
method public static java.lang.String prioritySendersToString(int);
+ method public static java.lang.String suppressedEffectsToString(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5160,9 +5182,13 @@
field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+ field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+ field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
field public final int priorityCallSenders;
field public final int priorityCategories;
field public final int priorityMessageSenders;
+ field public final int suppressedVisualEffects;
}
public final class PendingIntent implements android.os.Parcelable {
@@ -9920,6 +9946,7 @@
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 +17803,7 @@
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();
@@ -28972,12 +29000,15 @@
field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+ field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+ field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
}
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
method public java.lang.String getKey();
method public int getRank();
+ method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 0605851..203df90 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -424,6 +424,7 @@
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 @@
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 @@
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 @@
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
@@ -5264,10 +5284,12 @@
}
public static class NotificationManager.Policy implements android.os.Parcelable {
- ctor public NotificationManager.Policy(int, int, int);
+ ctor public deprecated NotificationManager.Policy(int, int, int);
+ ctor public NotificationManager.Policy(int, int, int, int);
method public int describeContents();
method public static java.lang.String priorityCategoriesToString(int);
method public static java.lang.String prioritySendersToString(int);
+ method public static java.lang.String suppressedEffectsToString(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5278,9 +5300,13 @@
field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+ field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+ field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
field public final int priorityCallSenders;
field public final int priorityCategories;
field public final int priorityMessageSenders;
+ field public final int suppressedVisualEffects;
}
public final class PendingIntent implements android.os.Parcelable {
@@ -10261,6 +10287,7 @@
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 +21521,13 @@
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 {
@@ -31089,6 +31119,8 @@
field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+ field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+ field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
field public static final int TRIM_FULL = 0; // 0x0
field public static final int TRIM_LIGHT = 1; // 0x1
}
@@ -31097,6 +31129,7 @@
ctor public NotificationListenerService.Ranking();
method public java.lang.String getKey();
method public int getRank();
+ method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
}
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7f33cb5..62e0919a 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.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.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 @@
@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 @@
"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 @@
}
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 @@
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);
- }
+ return false;
}
-
- 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);
+ return true;
}
- }
- 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);
- }
- }
- 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 9d6aa13..273483a 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 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 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 @@
private WindowManager mWindowManager;
+ private MagnificationController mMagnificationController;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -396,6 +408,20 @@
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 @@
}
/**
+ * 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 @@
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
- AccessibilityService.this.onServiceConnected();
+ AccessibilityService.this.dispatchServiceConnected();
}
@Override
@@ -678,6 +1083,12 @@
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 @@
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 @@
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 @@
}
} 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 4edb0c6..2c98fef 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -104,6 +104,12 @@
*/
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 @@
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 @@
.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 @@
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 8b503dd..15666bf 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 @@
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 5f7a17d..6ac50bd 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@
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 @@
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 65de4ca..00bba2d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -327,12 +327,39 @@
/** @hide Process is being cached for later use and is empty. */
public static final int PROCESS_STATE_CACHED_EMPTY = 16;
+ /** @hide Should this process state be considered a background state? */
+ public static final boolean isProcStateBackground(int procState) {
+ return procState >= PROCESS_STATE_BACKUP;
+ }
+
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
/** @hide requestType for assist context: generate full AssistStructure. */
public static final int ASSIST_CONTEXT_FULL = 1;
+ /** @hide Flag for registerUidObserver: report changes in process state. */
+ public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+ /** @hide Flag for registerUidObserver: report uid gone. */
+ public static final int UID_OBSERVER_GONE = 1<<1;
+
+ /** @hide Flag for registerUidObserver: report uid has become idle. */
+ public static final int UID_OBSERVER_IDLE = 1<<2;
+
+ /** @hide Flag for registerUidObserver: report uid has become active. */
+ public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+ /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+ public static final int APP_START_MODE_NORMAL = 0;
+
+ /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+ public static final int APP_START_MODE_DELAYED = 1;
+
+ /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+ * launches. */
+ public static final int APP_START_MODE_DISABLED = 2;
+
/**
* Lock task mode is not active.
*/
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4449e4f..e246e62 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -756,7 +756,7 @@
return true;
}
- case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+ case MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final int stackId = data.readInt();
final Rect r = Rect.CREATOR.createFromParcel(data);
@@ -2029,7 +2029,8 @@
data.enforceInterface(IActivityManager.descriptor);
IUidObserver observer = IUidObserver.Stub.asInterface(
data.readStrongBinder());
- registerUidObserver(observer);
+ int which = data.readInt();
+ registerUidObserver(observer, which);
return true;
}
@@ -2698,13 +2699,22 @@
reply.writeNoException();
return true;
}
- case REMOVE_STACK: {
+ case REMOVE_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final int stackId = data.readInt();
removeStack(stackId);
reply.writeNoException();
return true;
}
+ case GET_APP_START_MODE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int uid = data.readInt();
+ final String pkg = data.readString();
+ int res = getAppStartMode(uid, pkg);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -3579,7 +3589,7 @@
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(stackId);
r.writeToParcel(data, 0);
- mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+ mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION, data, reply, 0);
reply.readException();
final boolean res = reply.readInt() != 0;
data.recycle();
@@ -5326,11 +5336,12 @@
reply.recycle();
}
- public void registerUidObserver(IUidObserver observer) throws RemoteException {
+ public void registerUidObserver(IUidObserver observer, int which) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ data.writeInt(which);
mRemote.transact(REGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -6292,11 +6303,26 @@
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(stackId);
- mRemote.transact(REMOVE_STACK, data, reply, 0);
+ mRemote.transact(REMOVE_STACK_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
+ @Override
+ public int getAppStartMode(int uid, String packageName) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(uid);
+ data.writeString(packageName);
+ mRemote.transact(GET_APP_START_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 77a9795..f0453e9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -237,8 +237,10 @@
public static final int OP_TURN_SCREEN_ON = 61;
/** @hide Get device accounts. */
public static final int OP_GET_ACCOUNTS = 62;
+ /** @hide Control whether an application is allowed to run in the background. */
+ public static final int OP_RUN_IN_BACKGROUND = 63;
/** @hide */
- public static final int _NUM_OP = 63;
+ public static final int _NUM_OP = 64;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -409,6 +411,7 @@
OP_WRITE_EXTERNAL_STORAGE,
OP_TURN_SCREEN_ON,
OP_GET_ACCOUNTS,
+ OP_RUN_IN_BACKGROUND,
};
/**
@@ -478,7 +481,8 @@
OPSTR_READ_EXTERNAL_STORAGE,
OPSTR_WRITE_EXTERNAL_STORAGE,
null,
- OPSTR_GET_ACCOUNTS
+ OPSTR_GET_ACCOUNTS,
+ null,
};
/**
@@ -549,6 +553,7 @@
"WRITE_EXTERNAL_STORAGE",
"TURN_ON_SCREEN",
"GET_ACCOUNTS",
+ "RUN_IN_BACKGROUND",
};
/**
@@ -618,7 +623,8 @@
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
null, // no permission for turning the screen on
- Manifest.permission.GET_ACCOUNTS
+ Manifest.permission.GET_ACCOUNTS,
+ null, // no permission for running in background
};
/**
@@ -690,6 +696,7 @@
null, // WRITE_EXTERNAL_STORAGE
null, // TURN_ON_SCREEN
null, // GET_ACCOUNTS
+ null, // RUN_IN_BACKGROUND
};
/**
@@ -760,6 +767,7 @@
false, // WRITE_EXTERNAL_STORAGE
false, // TURN_ON_SCREEN
false, // GET_ACCOUNTS
+ false, // RUN_IN_BACKGROUND
};
/**
@@ -829,6 +837,7 @@
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN
AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
};
/**
@@ -901,7 +910,8 @@
false,
false,
false,
- false
+ false,
+ false,
};
/**
@@ -1329,7 +1339,7 @@
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
- public void opChanged(int op, String packageName) {
+ public void opChanged(int op, int uid, String packageName) {
if (callback instanceof OnOpChangedInternalListener) {
((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b69a480..3d0fc92 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -400,7 +400,7 @@
public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
- public void registerUidObserver(IUidObserver observer) throws RemoteException;
+ public void registerUidObserver(IUidObserver observer, int which) throws RemoteException;
public void unregisterUidObserver(IUidObserver observer) throws RemoteException;
public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException;
@@ -542,6 +542,8 @@
public void removeStack(int stackId) throws RemoteException;
+ public int getAppStartMode(int uid, String packageName) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -899,6 +901,7 @@
int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
- int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348;
- int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349;
+ int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
+ int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
+ int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
}
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 308cb94..fa8d0c9 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,6 +18,24 @@
/** {@hide} */
oneway interface IUidObserver {
+ /**
+ * General report of a state change of an uid.
+ */
void onUidStateChanged(int uid, int procState);
+
+ /**
+ * Report that there are no longer any processes running for a uid.
+ */
void onUidGone(int uid);
+
+ /**
+ * Report that a uid is now active (no longer idle).
+ */
+ void onUidActive(int uid);
+
+ /**
+ * Report that a uid is idle -- it has either been running in the background for
+ * a sufficient period of time, or all of its processes have gone away.
+ */
+ void onUidIdle(int uid);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 07b4d39..043c503 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -610,15 +610,39 @@
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
public final int priorityMessageSenders;
+ public static final int SUPPRESSED_EFFECTS_UNSET = -1;
+ public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 0;
+ public static final int SUPPRESSED_EFFECT_PEEK = 1 << 1;
+
+ private static final int[] ALL_SUPPRESSED_EFFECTS = {
+ SUPPRESSED_EFFECT_LIGHTS,
+ SUPPRESSED_EFFECT_PEEK,
+ };
+
+ /**
+ * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode.
+ * Bitmask of SUPPRESSED_EFFECT_* constants.
+ */
+ public final int suppressedVisualEffects;
+
+
+ @Deprecated
public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) {
+ this(priorityCategories, priorityCallSenders, priorityMessageSenders,
+ SUPPRESSED_EFFECTS_UNSET);
+ }
+
+ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
+ int suppressedVisualEffects) {
this.priorityCategories = priorityCategories;
this.priorityCallSenders = priorityCallSenders;
this.priorityMessageSenders = priorityMessageSenders;
+ this.suppressedVisualEffects = suppressedVisualEffects;
}
/** @hide */
public Policy(Parcel source) {
- this(source.readInt(), source.readInt(), source.readInt());
+ this(source.readInt(), source.readInt(), source.readInt(), source.readInt());
}
@Override
@@ -626,6 +650,7 @@
dest.writeInt(priorityCategories);
dest.writeInt(priorityCallSenders);
dest.writeInt(priorityMessageSenders);
+ dest.writeInt(suppressedVisualEffects);
}
@Override
@@ -635,7 +660,8 @@
@Override
public int hashCode() {
- return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders);
+ return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders,
+ suppressedVisualEffects);
}
@Override
@@ -645,7 +671,8 @@
final Policy other = (Policy) o;
return other.priorityCategories == priorityCategories
&& other.priorityCallSenders == priorityCallSenders
- && other.priorityMessageSenders == priorityMessageSenders;
+ && other.priorityMessageSenders == priorityMessageSenders
+ && other.suppressedVisualEffects == suppressedVisualEffects;
}
@Override
@@ -654,9 +681,29 @@
+ "priorityCategories=" + priorityCategoriesToString(priorityCategories)
+ ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders)
+ ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
+ + ",suppressedVisualEffects="
+ + suppressedEffectsToString(suppressedVisualEffects)
+ "]";
}
+ public static String suppressedEffectsToString(int effects) {
+ if (effects <= 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) {
+ final int effect = ALL_SUPPRESSED_EFFECTS[i];
+ if ((effects & effect) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(effectToString(effect));
+ }
+ effects &= ~effect;
+ }
+ if (effects != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("UNKNOWN_").append(effects);
+ }
+ return sb.toString();
+ }
+
public static String priorityCategoriesToString(int priorityCategories) {
if (priorityCategories == 0) return "";
final StringBuilder sb = new StringBuilder();
@@ -675,6 +722,15 @@
return sb.toString();
}
+ private static String effectToString(int effect) {
+ switch (effect) {
+ case SUPPRESSED_EFFECT_LIGHTS: return "SUPPRESSED_EFFECT_LIGHTS";
+ case SUPPRESSED_EFFECT_PEEK: return "SUPPRESSED_EFFECT_PEEK";
+ case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET";
+ default: return "UNKNOWN_" + effect;
+ }
+ }
+
private static String priorityCategoryToString(int priorityCategory) {
switch (priorityCategory) {
case PRIORITY_CATEGORY_REMINDERS: return "PRIORITY_CATEGORY_REMINDERS";
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a3b3022..aca0763 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -23,10 +23,8 @@
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 @@
* @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 @@
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 @@
mTimePicker.setCurrentHour(mInitialHourOfDay);
mTimePicker.setCurrentMinute(mInitialMinute);
mTimePicker.setOnTimeChangedListener(this);
- mTimePicker.setValidationCallback(mValidationCallback);
}
@Override
@@ -181,14 +177,4 @@
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 efed2e0..f7848f9 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -21,9 +21,11 @@
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 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 25d9aa9..09a15de 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -685,6 +685,48 @@
}
/**
+ * 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 0e23fad..0bb4088 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,6 +50,8 @@
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 68b77fe..30fe531 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
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 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 @@
/**
* 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 @@
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 9c880d3..eda4136 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -479,6 +479,14 @@
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 @@
/**
* @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 0606e35..7b3dde4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -48,7 +48,6 @@
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 java.io.IOException;
import java.io.InputStream;
-import java.lang.ref.WeakReference;
import java.util.Locale;
/**
@@ -120,9 +118,6 @@
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 @@
static Resources mSystem = null;
private static boolean sPreloaded;
- private static int sPreloadedDensity;
+
+ /** Lock object used to protect access to caches and configuration. */
+ private final Object mAccessLock = new Object();
// These are protected by mAccessLock.
- private final Object mAccessLock = new Object();
private final Configuration mTmpConfig = new Configuration();
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
@@ -147,7 +143,12 @@
/** 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 NotFoundException(String name) {
super(name);
}
+
+ public NotFoundException(String name, Exception cause) {
+ super(name, cause);
+ }
}
/**
@@ -621,18 +626,15 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* 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);
+ 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);
}
- final Drawable res = loadDrawable(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
- return res;
}
/**
@@ -849,7 +830,8 @@
*/
@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 @@
*/
@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 @@
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 @@
*/
@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 @@
@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 TypedValue value = obtainTempTypedValue(id);
+ try {
+ return loadColorStateList(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
-
- final ColorStateList res = loadColorStateList(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
-
- return res;
}
/**
@@ -1078,19 +1025,16 @@
* @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 @@
* @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 @@
* @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 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 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 @@
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 @@
}
sPreloaded = true;
mPreloading = true;
- sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
- mConfiguration.densityDpi = sPreloadedDensity;
+ mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
updateConfiguration(null, null);
}
}
@@ -2740,19 +2698,16 @@
/*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 f55883a..7a1a6a2 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -35,4 +35,6 @@
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 4ac361d0..54bfca3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -632,9 +632,6 @@
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 cad482b..6f12b62 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -48,19 +48,35 @@
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 95da438..f946ca7 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -256,6 +256,23 @@
}
}
+ /** @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 d19c7c9..2e43ffc 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -21,8 +21,12 @@
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 @@
}
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 @@
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 @@
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 @@
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 27df46d..2d9090b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,6 +30,7 @@
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 @@
/**
* 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 @@
* 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 @@
* 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 @@
/**
* 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 @@
* 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 @@
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/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7e7b5fc..ee97e8e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -108,6 +108,11 @@
* This does not change the interruption filter, only the effects. **/
public static final int HINT_HOST_DISABLE_EFFECTS = 1;
+ public static final int SUPPRESSED_EFFECT_LIGHTS =
+ NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+ public static final int SUPPRESSED_EFFECT_PEEK =
+ NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
/**
* The full trim of the StatusBarNotification including all its features.
*
@@ -822,6 +827,7 @@
private boolean mIsAmbient;
private boolean mMatchesInterruptionFilter;
private int mVisibilityOverride;
+ private int mSuppressedVisualEffects;
public Ranking() {}
@@ -861,6 +867,14 @@
return mVisibilityOverride;
}
+ /**
+ * Returns the type(s) of visual effects that should be suppressed for this notification.
+ * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK}}.
+ */
+ public int getSuppressedVisualEffects() {
+ return mSuppressedVisualEffects;
+ }
+
/**
* Returns whether the notification matches the user's interruption
@@ -874,12 +888,14 @@
}
private void populate(String key, int rank, boolean isAmbient,
- boolean matchesInterruptionFilter, int visibilityOverride) {
+ boolean matchesInterruptionFilter, int visibilityOverride,
+ int suppressedVisualEffects) {
mKey = key;
mRank = rank;
mIsAmbient = isAmbient;
mMatchesInterruptionFilter = matchesInterruptionFilter;
mVisibilityOverride = visibilityOverride;
+ mSuppressedVisualEffects = suppressedVisualEffects;
}
}
@@ -896,6 +912,7 @@
private ArrayMap<String,Integer> mRanks;
private ArraySet<Object> mIntercepted;
private ArrayMap<String, Integer> mVisibilityOverrides;
+ private ArrayMap<String, Integer> mSuppressedVisualEffects;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -921,7 +938,7 @@
public boolean getRanking(String key, Ranking outRanking) {
int rank = getRank(key);
outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
- getVisibilityOverride(key));
+ getVisibilityOverride(key), getSuppressedVisualEffects(key));
return rank >= 0;
}
@@ -959,11 +976,24 @@
buildVisibilityOverridesLocked();
}
}
- Integer overide = mVisibilityOverrides.get(key);
- if (overide == null) {
+ Integer override = mVisibilityOverrides.get(key);
+ if (override == null) {
return Ranking.VISIBILITY_NO_OVERRIDE;
}
- return overide.intValue();
+ return override.intValue();
+ }
+
+ private int getSuppressedVisualEffects(String key) {
+ synchronized (this) {
+ if (mSuppressedVisualEffects == null) {
+ buildSuppressedVisualEffectsLocked();
+ }
+ }
+ Integer suppressed = mSuppressedVisualEffects.get(key);
+ if (suppressed == null) {
+ return 0;
+ }
+ return suppressed.intValue();
}
// Locked by 'this'
@@ -992,6 +1022,15 @@
}
}
+ // Locked by 'this'
+ private void buildSuppressedVisualEffectsLocked() {
+ Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
+ mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
+ for (String key: suppressedBundle.keySet()) {
+ mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 6fba900..1282fb1 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,13 +28,15 @@
private final String[] mInterceptedKeys;
private final int mFirstAmbientIndex;
private final Bundle mVisibilityOverrides;
+ private final Bundle mSuppressedVisualEffects;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
- Bundle visibilityOverrides, int firstAmbientIndex) {
+ Bundle visibilityOverrides, int firstAmbientIndex, Bundle suppressedVisualEffects) {
mKeys = keys;
mFirstAmbientIndex = firstAmbientIndex;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
+ mSuppressedVisualEffects = suppressedVisualEffects;
}
public NotificationRankingUpdate(Parcel in) {
@@ -42,6 +44,7 @@
mFirstAmbientIndex = in.readInt();
mInterceptedKeys = in.readStringArray();
mVisibilityOverrides = in.readBundle();
+ mSuppressedVisualEffects = in.readBundle();
}
@Override
@@ -55,6 +58,7 @@
out.writeInt(mFirstAmbientIndex);
out.writeStringArray(mInterceptedKeys);
out.writeBundle(mVisibilityOverrides);
+ out.writeBundle(mSuppressedVisualEffects);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -83,4 +87,8 @@
public Bundle getVisibilityOverrides() {
return mVisibilityOverrides;
}
+
+ public Bundle getSuppressedVisualEffects() {
+ return mSuppressedVisualEffects;
+ }
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 82f1b28..b3399d0 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -77,6 +77,8 @@
private static final boolean DEFAULT_ALLOW_REMINDERS = true;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
+ private static final boolean DEFAULT_ALLOW_PEEK = true;
+ private static final boolean DEFAULT_ALLOW_LIGHTS = true;
private static final int XML_VERSION = 2;
private static final String ZEN_TAG = "zen";
@@ -91,6 +93,8 @@
private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom";
private static final String ALLOW_ATT_REMINDERS = "reminders";
private static final String ALLOW_ATT_EVENTS = "events";
+ private static final String ALLOW_ATT_PEEK = "peek";
+ private static final String ALLOW_ATT_LIGHTS = "lights";
private static final String CONDITION_TAG = "condition";
private static final String CONDITION_ATT_COMPONENT = "component";
@@ -122,6 +126,8 @@
public int allowCallsFrom = DEFAULT_SOURCE;
public int allowMessagesFrom = DEFAULT_SOURCE;
public int user = UserHandle.USER_SYSTEM;
+ public boolean allowPeek = DEFAULT_ALLOW_PEEK;
+ public boolean allowLights = DEFAULT_ALLOW_LIGHTS;
public ZenRule manualRule;
public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
@@ -148,6 +154,8 @@
automaticRules.put(ids[i], rules[i]);
}
}
+ allowPeek = source.readInt() == 1;
+ allowLights = source.readInt() == 1;
}
@Override
@@ -175,22 +183,26 @@
} else {
dest.writeInt(0);
}
+ dest.writeInt(allowPeek ? 1 : 0);
+ dest.writeInt(allowLights ? 1 : 0);
}
@Override
public String toString() {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
- .append("user=").append(user)
- .append(",allowCalls=").append(allowCalls)
- .append(",allowRepeatCallers=").append(allowRepeatCallers)
- .append(",allowMessages=").append(allowMessages)
- .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
- .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
- .append(",allowReminders=").append(allowReminders)
- .append(",allowEvents=").append(allowEvents)
- .append(",automaticRules=").append(automaticRules)
- .append(",manualRule=").append(manualRule)
- .append(']').toString();
+ .append("user=").append(user)
+ .append(",allowCalls=").append(allowCalls)
+ .append(",allowRepeatCallers=").append(allowRepeatCallers)
+ .append(",allowMessages=").append(allowMessages)
+ .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
+ .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
+ .append(",allowReminders=").append(allowReminders)
+ .append(",allowEvents=").append(allowEvents)
+ .append(",allowPeek=").append(allowPeek)
+ .append(",allowLights=").append(allowLights)
+ .append(",automaticRules=").append(automaticRules)
+ .append(",manualRule=").append(manualRule)
+ .append(']').toString();
}
private Diff diff(ZenModeConfig to) {
@@ -222,6 +234,12 @@
if (allowEvents != to.allowEvents) {
d.addLine("allowEvents", allowEvents, to.allowEvents);
}
+ if (allowPeek != to.allowPeek) {
+ d.addLine("allowPeek", allowPeek, to.allowPeek);
+ }
+ if (allowLights != to.allowLights) {
+ d.addLine("allowLights", allowLights, to.allowLights);
+ }
final ArraySet<String> allRules = new ArraySet<>();
addKeys(allRules, automaticRules);
addKeys(allRules, to.automaticRules);
@@ -319,6 +337,8 @@
&& other.allowMessagesFrom == allowMessagesFrom
&& other.allowReminders == allowReminders
&& other.allowEvents == allowEvents
+ && other.allowPeek == allowPeek
+ && other.allowLights == allowLights
&& other.user == user
&& Objects.equals(other.automaticRules, automaticRules)
&& Objects.equals(other.manualRule, manualRule);
@@ -327,7 +347,8 @@
@Override
public int hashCode() {
return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
- allowMessagesFrom, allowReminders, allowEvents, user, automaticRules, manualRule);
+ allowMessagesFrom, allowReminders, allowEvents, allowPeek, allowLights,
+ user, automaticRules, manualRule);
}
private static String toDayList(int[] days) {
@@ -412,6 +433,8 @@
rt.allowCallsFrom = DEFAULT_SOURCE;
rt.allowMessagesFrom = DEFAULT_SOURCE;
}
+ rt.allowPeek = safeBoolean(parser, ALLOW_ATT_PEEK, DEFAULT_ALLOW_PEEK);
+ rt.allowLights = safeBoolean(parser, ALLOW_ATT_LIGHTS, DEFAULT_ALLOW_LIGHTS);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
} else if (AUTOMATIC_TAG.equals(tag)) {
@@ -440,6 +463,8 @@
out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom));
out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
+ out.attribute(null, ALLOW_ATT_PEEK, Boolean.toString(allowPeek));
+ out.attribute(null, ALLOW_ATT_LIGHTS, Boolean.toString(allowLights));
out.endTag(null, ALLOW_TAG);
if (manualRule != null) {
@@ -611,9 +636,17 @@
if (allowRepeatCallers) {
priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
}
+ int suppressedVisualEffects = 0;
+ if (!allowPeek) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK;
+ }
+ if (!allowLights) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+ }
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
- return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders);
+ return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
+ suppressedVisualEffects);
}
private static int sourceToPrioritySenders(int source, int def) {
@@ -645,6 +678,10 @@
allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders,
allowMessagesFrom);
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ allowPeek = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) == 0;
+ allowLights = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) == 0;
+ }
}
public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
index 0ee6714..49242bb 100644
--- a/core/java/android/view/MagnificationSpec.java
+++ b/core/java/android/view/MagnificationSpec.java
@@ -28,10 +28,21 @@
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 @@
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 @@
}
@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 fdea1ba..cd9dd97 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.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.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 @@
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 @@
& 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 @@
}
int getHostVisibility() {
- return mAppVisible ? mView.getVisibility() : View.GONE;
+ return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
}
/**
@@ -1568,7 +1575,7 @@
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 @@
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 edf4297..1521f2e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,8 +118,7 @@
*/
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 @@
/**
* 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 3882bca..89b1eb9 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 @@
* 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 @@
*
* @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 34a8439..a53df88 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.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.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 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 @@
* 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 @@
/**
* @return the gravity used to align the popup window to its anchor view
- *
* @see #setGravity(int)
*/
public int getGravity() {
@@ -146,8 +157,8 @@
* 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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
}
/**
- * 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 @@
/**
* Show the menu popup anchored to the view specified during construction.
+ *
* @see #dismiss()
*/
public void show() {
@@ -223,6 +236,7 @@
/**
* Dismiss the menu popup.
+ *
* @see #show()
*/
public void dismiss() {
@@ -230,68 +244,49 @@
}
/**
- * 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
- */
- 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) {
- }
-
- /**
- * Interface responsible for receiving menu item click events if the items themselves
- * do not have individual item click listeners.
+ * Interface responsible for receiving menu item click events if the items
+ * themselves do not have individual item click listeners.
*/
public interface OnMenuItemClickListener {
/**
- * This method will be invoked when a menu item is clicked if the item itself did
- * not already handle the event.
+ * This method will be invoked when a menu item is clicked if the item
+ * itself did not already handle the event.
*
- * @param item {@link MenuItem} that was clicked
- * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+ * @param item the menu item that was clicked
+ * @return {@code true} if the event was handled, {@code false}
+ * otherwise
*/
- public boolean onMenuItemClick(MenuItem item);
+ boolean onMenuItemClick(MenuItem item);
+ }
+
+ /**
+ * 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 popup menu that was dismissed
+ */
+ void onDismiss(PopupMenu menu);
}
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e5af79..a24d37f 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -100,7 +100,7 @@
* @see #getHour()
*/
public void setHour(int hour) {
- mDelegate.setCurrentHour(hour);
+ mDelegate.setHour(hour);
}
/**
@@ -110,7 +110,7 @@
* @see #setHour(int)
*/
public int getHour() {
- return mDelegate.getCurrentHour();
+ return mDelegate.getHour();
}
/**
@@ -120,7 +120,7 @@
* @see #getMinute()
*/
public void setMinute(int minute) {
- mDelegate.setCurrentMinute(minute);
+ mDelegate.setMinute(minute);
}
/**
@@ -130,7 +130,7 @@
* @see #setMinute(int)
*/
public int getMinute() {
- return mDelegate.getCurrentMinute();
+ return mDelegate.getMinute();
}
/**
@@ -150,7 +150,7 @@
@NonNull
@Deprecated
public Integer getCurrentHour() {
- return mDelegate.getCurrentHour();
+ return mDelegate.getHour();
}
/**
@@ -160,7 +160,7 @@
*/
@Deprecated
public void setCurrentMinute(@NonNull Integer currentMinute) {
- mDelegate.setCurrentMinute(currentMinute);
+ mDelegate.setMinute(currentMinute);
}
/**
@@ -170,7 +170,7 @@
@NonNull
@Deprecated
public Integer getCurrentMinute() {
- return mDelegate.getCurrentMinute();
+ return mDelegate.getMinute();
}
/**
@@ -186,7 +186,7 @@
return;
}
- mDelegate.setIs24HourView(is24HourView);
+ mDelegate.setIs24Hour(is24HourView);
}
/**
@@ -195,7 +195,7 @@
* @see #setIs24HourView(Boolean)
*/
public boolean is24HourView() {
- return mDelegate.is24HourView();
+ return mDelegate.is24Hour();
}
/**
@@ -207,16 +207,6 @@
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 @@
}
@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 @@
* 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 @@
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 @@
* An abstract class which can be used as a start for TimePicker implementations
*/
abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
- // The delegator
- protected TimePicker mDelegator;
+ protected final TimePicker mDelegator;
+ protected final Context mContext;
+ protected final Locale mLocale;
- // The context
- protected Context mContext;
-
- // 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 4dc5fd3e..b523b84 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -19,7 +19,6 @@
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 @@
private boolean mAllowAutoAdvance;
private int mInitialHourOfDay;
private int mInitialMinute;
- private boolean mIs24HourView;
+ private boolean mIs24Hour;
private boolean mIsAmPmAtStart;
// Accessibility strings.
@@ -199,20 +198,13 @@
mAllowAutoAdvance = true;
- // Updates mHourFormat variables used below.
- updateHourFormat(mCurrentLocale, mIs24HourView);
-
- // Update hour text field.
- final int minHour = mHourFormatStartsAtZero ? 0 : 1;
- final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
- mHourView.setRange(minHour, maxHour);
- mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+ updateHourFormat();
// 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);
}
/**
@@ -233,15 +225,14 @@
}
/**
- * Determines how the hour should be formatted and updates member variables
- * related to hour formatting.
- *
- * @param locale the locale in which the view is displayed
- * @param is24Hour whether the view is in 24-hour (hour-of-day) mode
+ * Updates hour formatting based on the current locale and 24-hour mode.
+ * <p>
+ * Determines how the hour should be formatted, sets member variables for
+ * leading zero and starting hour, and sets the hour view's presentation.
*/
- private void updateHourFormat(Locale locale, boolean is24Hour) {
+ private void updateHourFormat() {
final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
- locale, is24Hour ? "Hm" : "hm");
+ mLocale, mIs24Hour ? "Hm" : "hm");
final int lengthPattern = bestDateTimePattern.length();
boolean showLeadingZero = false;
char hourFormat = '\0';
@@ -259,6 +250,12 @@
mHourFormatShowLeadingZero = showLeadingZero;
mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
+
+ // Update hour text field.
+ final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+ final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
+ mHourView.setRange(minHour, maxHour);
+ mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
}
private static final CharSequence obtainVerbatim(String text) {
@@ -333,7 +330,7 @@
private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
- mIs24HourView = is24HourView;
+ mIs24Hour = is24HourView;
updateUI(index);
}
@@ -352,17 +349,16 @@
}
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 +391,32 @@
* 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 +424,49 @@
* 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;
+ public void setIs24Hour(boolean is24Hour) {
+ if (mIs24Hour != is24Hour) {
+ mIs24Hour = is24Hour;
+ mInitialHourOfDay = getHour();
+
+ updateHourFormat();
+ updateUI(mRadialTimePickerView.getCurrentItemShowing());
}
-
- mIs24HourView = is24HourView;
- mInitialHourOfDay = getCurrentHour();
-
- 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 +496,9 @@
}
@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 +509,6 @@
}
@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 +517,13 @@
@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 +542,7 @@
private void onTimeChanged() {
mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator,
- getCurrentHour(), getCurrentMinute());
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
@@ -666,25 +648,29 @@
}
if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
/**
* Converts hour-of-day (0-23) time into a localized hour number.
+ * <p>
+ * The localized value may be in the range (0-23), (1-24), (0-11), or
+ * (1-12) depending on the locale. This method does not handle leading
+ * zeroes.
*
* @param hourOfDay the hour-of-day (0-23)
* @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 +702,8 @@
* 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 +805,14 @@
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 8741cc3..2ed230b 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 com.android.internal.R;
import java.util.Calendar;
-import java.util.Locale;
import libcore.icu.LocaleData;
@@ -92,7 +90,7 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
*/
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@Override
- public boolean is24HourView() {
+ public boolean is24Hour() {
return mIs24HourView;
}
@@ -388,20 +385,15 @@
}
@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 @@
} 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 @@
}
private void updateAmPmControl() {
- if (is24HourView()) {
+ if (is24Hour()) {
if (mAmPmSpinner != null) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
@@ -466,27 +458,16 @@
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 @@
}
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/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index afbc609..5fdc920 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
// This interface is also used by native code, so must
// be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
oneway interface IAppOpsCallback {
- void opChanged(int op, String packageName);
+ void opChanged(int op, int uid, String packageName);
}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 2c5e50c..08c7935 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -30,6 +30,9 @@
public static final int QS_LOCK_TILE = 257;
public static final int QS_USER_TILE = 258;
public static final int QS_BATTERY_TILE = 259;
+ public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
+ public static final int ACTION_ZEN_ALLOW_PEEK = 261;
+ public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index e26b27d..c067da7 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 @@
return;
}
- mArgs = args;
- mNextArg = 0;
- mCurArgData = null;
+ mArgs.init(null, null, null, null, args, 0);
try {
onRun();
@@ -87,32 +91,7 @@
* 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 @@
* 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 @@
* 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 197004c..8186378 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -39,10 +39,8 @@
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 3e86fac..a40f9a8 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -322,7 +322,7 @@
/**
* 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 @@
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/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
new file mode 100644
index 0000000..1eeb363
--- /dev/null
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -0,0 +1,289 @@
+/*
+ * 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.widget.NonClientDecorView;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+/**
+ * The thread which draws a fill in background while the app is resizing in areas where the app
+ * content draw is lagging behind the resize operation.
+ * It starts with the creation and it ends once someone calls destroy().
+ * Any size changes can be passed by a call to setTargetRect will passed to the thread and
+ * executed via the Choreographer.
+ * @hide
+ */
+public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
+
+ private NonClientDecorView mNonClientDecorView;
+
+ // This is containing the last requested size by a resize command. Note that this size might
+ // or might not have been applied to the output already.
+ private final Rect mTargetRect = new Rect();
+
+ // The render nodes for the multi threaded renderer.
+ private ThreadedRenderer mRenderer;
+ private RenderNode mFrameAndBackdropNode;
+
+ private final Rect mOldTargetRect = new Rect();
+ private final Rect mNewTargetRect = new Rect();
+ private Choreographer mChoreographer;
+
+ // Cached size values from the last render for the case that the view hierarchy is gone
+ // during a configuration change.
+ private int mLastContentWidth;
+ private int mLastContentHeight;
+ private int mLastCaptionHeight;
+ private int mLastXOffset;
+ private int mLastYOffset;
+
+ // Whether to report when next frame is drawn or not.
+ private boolean mReportNextDraw;
+
+ public BackdropFrameRenderer(NonClientDecorView nonClientDecorView,
+ ThreadedRenderer renderer,
+ Rect initialBounds) {
+ mNonClientDecorView = nonClientDecorView;
+ setName("ResizeFrame");
+ mRenderer = renderer;
+
+ // Create a render node for the content and frame backdrop
+ // which can be resized independently from the content.
+ mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
+
+ mRenderer.addRenderNode(mFrameAndBackdropNode, true);
+
+ // Set the initial bounds and draw once so that we do not get a broken frame.
+ mTargetRect.set(initialBounds);
+ synchronized (this) {
+ changeWindowSizeLocked(initialBounds);
+ }
+
+ // Kick off our draw thread.
+ start();
+ }
+
+ /**
+ * Call this function asynchronously when the window size has been changed. The change will
+ * be picked up once per frame and the frame will be re-rendered accordingly.
+ * @param newTargetBounds The new target bounds.
+ */
+ public void setTargetRect(Rect newTargetBounds) {
+ synchronized (this) {
+ mTargetRect.set(newTargetBounds);
+ // Notify of a bounds change.
+ pingRenderLocked();
+ }
+ }
+
+ /**
+ * The window got replaced due to a configuration change.
+ */
+ public void onConfigurationChange() {
+ synchronized (this) {
+ if (mRenderer != null) {
+ // Enforce a window redraw.
+ mOldTargetRect.set(0, 0, 0, 0);
+ pingRenderLocked();
+ }
+ }
+ }
+
+ /**
+ * All resources of the renderer will be released. This function can be called from the
+ * the UI thread as well as the renderer thread.
+ */
+ public void releaseRenderer() {
+ synchronized (this) {
+ if (mRenderer != null) {
+ // Invalidate the current content bounds.
+ mRenderer.setContentDrawBounds(0, 0, 0, 0);
+
+ // Remove the render node again
+ // (see comment above - better to do that only once).
+ mRenderer.removeRenderNode(mFrameAndBackdropNode);
+
+ mRenderer = null;
+
+ // Exit the renderer loop.
+ pingRenderLocked();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ Looper.prepare();
+ synchronized (this) {
+ mChoreographer = Choreographer.getInstance();
+
+ // Draw at least once.
+ mChoreographer.postFrameCallback(this);
+ }
+ Looper.loop();
+ } finally {
+ releaseRenderer();
+ }
+ synchronized (this) {
+ // Make sure no more messages are being sent.
+ mChoreographer = null;
+ }
+ }
+
+ /**
+ * The implementation of the FrameCallback.
+ * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+ * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
+ */
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ synchronized (this) {
+ if (mRenderer == null) {
+ reportDrawIfNeeded();
+ // Tell the looper to stop. We are done.
+ Looper.myLooper().quit();
+ return;
+ }
+ mNewTargetRect.set(mTargetRect);
+ if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
+ mOldTargetRect.set(mNewTargetRect);
+ changeWindowSizeLocked(mNewTargetRect);
+ }
+ }
+ }
+
+ /**
+ * The content is about to be drawn and we got the location of where it will be shown.
+ * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
+ * if the previous call was ignored since the size was unknown.
+ * @param xOffset The x offset where the content is drawn to.
+ * @param yOffset The y offset where the content is drawn to.
+ * @param xSize The width size of the content. This should not be 0.
+ * @param ySize The height of the content.
+ * @return true if a frame should be requested after the content is drawn; false otherwise.
+ */
+ public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
+ synchronized (this) {
+ final boolean firstCall = mLastContentWidth == 0;
+ // The current content buffer is drawn here.
+ mLastContentWidth = xSize;
+ mLastContentHeight = ySize - mLastCaptionHeight;
+ mLastXOffset = xOffset;
+ mLastYOffset = yOffset;
+
+ mRenderer.setContentDrawBounds(
+ mLastXOffset,
+ mLastYOffset,
+ mLastXOffset + mLastContentWidth,
+ mLastYOffset + mLastCaptionHeight + mLastContentHeight);
+ // If this was the first call and changeWindowSizeLocked got already called prior
+ // to us, we should re-issue a changeWindowSizeLocked now.
+ return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor);
+ }
+ }
+
+ public void onRequestDraw(boolean reportNextDraw) {
+ synchronized (this) {
+ mReportNextDraw = reportNextDraw;
+ mOldTargetRect.set(0, 0, 0, 0);
+ pingRenderLocked();
+ }
+ }
+
+ /**
+ * Resizing the frame to fit the new window size.
+ * @param newBounds The window bounds which needs to be drawn.
+ */
+ private void changeWindowSizeLocked(Rect newBounds) {
+ // While a configuration change is taking place the view hierarchy might become
+ // inaccessible. For that case we remember the previous metrics to avoid flashes.
+ // Note that even when there is no visible caption, the caption child will exist.
+ View caption = mNonClientDecorView.getChildAt(0);
+ if (caption != null) {
+ final int captionHeight = caption.getHeight();
+ // The caption height will probably never dynamically change while we are resizing.
+ // Once set to something other then 0 it should be kept that way.
+ if (captionHeight != 0) {
+ // Remember the height of the caption.
+ mLastCaptionHeight = captionHeight;
+ }
+ }
+ // Make sure that the other thread has already prepared the render draw calls for the
+ // content. If any size is 0, we have to wait for it to be drawn first.
+ if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) ||
+ mLastContentWidth == 0 || mLastContentHeight == 0) {
+ return;
+ }
+ // Since the surface is spanning the entire screen, we have to add the start offset of
+ // the bounds to get to the surface location.
+ final int left = mLastXOffset + newBounds.left;
+ final int top = mLastYOffset + newBounds.top;
+ final int width = newBounds.width();
+ final int height = newBounds.height();
+
+ mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
+
+ // Draw the caption and content backdrops in to our render node.
+ DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
+ mNonClientDecorView.mCaptionBackgroundDrawable.setBounds(
+ 0, 0, left + width, top + mLastCaptionHeight);
+ mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas);
+
+ // The backdrop: clear everything with the background. Clipping is done elsewhere.
+ mNonClientDecorView.mResizingBackgroundDrawable.setBounds(
+ 0, mLastCaptionHeight, left + width, top + height);
+ mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas);
+ mFrameAndBackdropNode.end(canvas);
+
+ // We need to render the node explicitly
+ mRenderer.drawRenderNode(mFrameAndBackdropNode);
+
+ reportDrawIfNeeded();
+ }
+
+ /**
+ * Notify view root that a frame has been drawn by us, if it has requested so.
+ */
+ private void reportDrawIfNeeded() {
+ if (mReportNextDraw) {
+ if (mNonClientDecorView.isAttachedToWindow()) {
+ mNonClientDecorView.getViewRootImpl().reportDrawFinish();
+ }
+ mReportNextDraw = false;
+ }
+ }
+
+ /**
+ * Sends a message to the renderer to wake up and perform the next action which can be
+ * either the next rendering or the self destruction if mRenderer is null.
+ * Note: This call must be synchronized.
+ */
+ private void pingRenderLocked() {
+ if (mChoreographer != null) {
+ mChoreographer.postFrameCallback(this);
+ }
+ }
+}
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 0000000..49ceced
--- /dev/null
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -0,0 +1,1788 @@
+/*
+ * 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 com.android.internal.widget.NonClientDecorView;
+
+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.os.RemoteException;
+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.LayoutInflater;
+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.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.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;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+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();
+
+ // This is the non client decor view for the window, containing the caption and window control
+ // buttons. The visibility of this decor depends on the workspace and the window type.
+ // If the window type does not require such a view, this member might be null.
+ NonClientDecorView mNonClientDecorView;
+
+ // The non client decor needs to adapt to the used workspace. Since querying and changing the
+ // workspace is expensive, this is the workspace value the window is currently set up for.
+ int mWorkspaceId;
+
+ 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 && 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(mWorkspaceId);
+ }
+
+ void setWindow(PhoneWindow phoneWindow) {
+ mWindow = phoneWindow;
+ Context context = getContext();
+ if (context instanceof DecorContext) {
+ DecorContext decorContext = (DecorContext) context;
+ decorContext.setPhoneWindow(mWindow);
+ }
+ }
+
+ void onConfigurationChanged() {
+ if (mNonClientDecorView != null) {
+ int workspaceId = getWorkspaceId();
+ if (mWorkspaceId != workspaceId) {
+ mWorkspaceId = workspaceId;
+ // We might have to change the kind of surface before we do anything else.
+ mNonClientDecorView.onConfigurationChanged(
+ ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
+ ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
+ enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
+ }
+ }
+ }
+
+ View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
+ mNonClientDecorView = createNonClientDecorView(inflater);
+ final View root = inflater.inflate(layoutResource, null);
+ if (mNonClientDecorView != null) {
+ if (mNonClientDecorView.getParent() == null) {
+ addView(mNonClientDecorView,
+ new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+ mNonClientDecorView.addView(root,
+ new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ } else {
+ addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+ mContentRoot = (ViewGroup) root;
+ return root;
+ }
+
+ // Free floating overlapping windows require a non client decor with a caption and shadow..
+ private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
+ NonClientDecorView nonClientDecorView = null;
+ for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
+ View view = getChildAt(i);
+ if (view instanceof NonClientDecorView) {
+ // The decor was most likely saved from a relaunch - so reuse it.
+ nonClientDecorView = (NonClientDecorView) view;
+ removeViewAt(i);
+ }
+ }
+ final WindowManager.LayoutParams attrs = mWindow.getAttributes();
+ boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+ attrs.type == TYPE_APPLICATION;
+ mWorkspaceId = getWorkspaceId();
+ // Only a non floating application window on one of the allowed workspaces can get a non
+ // client decor.
+ if (!mWindow.isFloating()
+ && isApplication
+ && ActivityManager.StackId.isStaticStack(mWorkspaceId)) {
+ // Dependent on the brightness of the used title we either use the
+ // dark or the light button frame.
+ if (nonClientDecorView == null) {
+ Context context = getContext();
+ TypedValue value = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
+ inflater = inflater.from(context);
+ if (Color.luminance(value.data) < 0.5) {
+ nonClientDecorView = (NonClientDecorView) inflater.inflate(
+ R.layout.non_client_decor_dark, null);
+ } else {
+ nonClientDecorView = (NonClientDecorView) inflater.inflate(
+ R.layout.non_client_decor_light, null);
+ }
+ }
+ nonClientDecorView.setPhoneWindow(mWindow,
+ ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
+ ActivityManager.StackId.hasWindowShadow(mWorkspaceId),
+ getResizingBackgroundDrawable(),
+ getContext().getDrawable(R.drawable.non_client_decor_title_focused));
+ }
+ // Tell the decor if it has a visible non client decor.
+ enableNonClientDecor(
+ nonClientDecorView != null && ActivityManager.StackId.hasWindowDecor(mWorkspaceId));
+
+ return nonClientDecorView;
+ }
+
+ /**
+ * Returns the color used to fill areas the app has not rendered content to yet when the user
+ * is resizing the window of an activity in multi-window mode.
+ * */
+ private Drawable getResizingBackgroundDrawable() {
+ final Context context = getContext();
+
+ if (mWindow.mBackgroundResource != 0) {
+ final Drawable drawable = context.getDrawable(mWindow.mBackgroundResource);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
+ if (mWindow.mBackgroundFallbackResource != 0) {
+ final Drawable fallbackDrawable =
+ context.getDrawable(mWindow.mBackgroundFallbackResource);
+ if (fallbackDrawable != null) {
+ return fallbackDrawable;
+ }
+ }
+
+ // We shouldn't really get here as the background fallback should be always available since
+ // it is defaulted by the system.
+ Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
+ return null;
+ }
+
+ /**
+ * Returns the Id of the workspace which contains this window.
+ * Note that if no workspace can be determined - which usually means that it was not
+ * created for an activity - the fullscreen workspace ID will be returned.
+ * @return Returns the workspace stack id which contains this window.
+ **/
+ private int getWorkspaceId() {
+ int workspaceId = INVALID_STACK_ID;
+ final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ workspaceId = callback.getWindowStackId();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
+ }
+ }
+ if (workspaceId == INVALID_STACK_ID) {
+ return FULLSCREEN_WORKSPACE_STACK_ID;
+ }
+ return workspaceId;
+ }
+
+ void clearContentView() {
+ if (mNonClientDecorView != null) {
+ if (mNonClientDecorView.getChildCount() > 1) {
+ mNonClientDecorView.removeViewAt(1);
+ }
+ } else {
+ // This window doesn't have non client decor, so we need to just remove the
+ // children of the decor view.
+ removeAllViews();
+ }
+ }
+
+ 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 83f810f..6e7e5cf 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,24 +16,14 @@
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 +45,9 @@
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,11 +56,7 @@
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;
import android.app.ActivityManager;
@@ -87,10 +67,7 @@
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;
import android.media.session.MediaController;
@@ -108,22 +85,17 @@
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 +114,6 @@
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 |
@@ -174,32 +144,21 @@
// view is requested, so we need to force the recreating without introducing an infinite loop.
private boolean mForceDecorInstall = false;
- // This is the non client decor view for the window, containing the caption and window control
- // buttons. The visibility of this decor depends on the workspace and the window type.
- // If the window type does not require such a view, this member might be null.
- NonClientDecorView mNonClientDecorView;
-
- // The non client decor needs to adapt to the used workspace. Since querying and changing the
- // workspace is expensive, this is the workspace value the window is currently set up for.
- int mWorkspaceId;
-
// 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 +190,13 @@
* 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;
@@ -247,12 +206,12 @@
private ProgressBar mHorizontalProgressBar;
- private int mBackgroundResource = 0;
- private int mBackgroundFallbackResource = 0;
+ int mBackgroundResource = 0;
+ int mBackgroundFallbackResource = 0;
private Drawable mBackgroundDrawable;
- private boolean mLoadEleveation = true;
+ private boolean mLoadElevation = true;
private float mElevation;
/** Whether window content should be clipped to the background outline. */
@@ -261,8 +220,8 @@
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 +231,9 @@
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 +271,6 @@
private long mBackgroundFadeDurationMillis = -1;
private Boolean mSharedElementsUseOverlay;
- private Rect mTempRect;
- private Rect mOutsets = new Rect();
-
private boolean mIsStartingWindow;
private int mTheme = -1;
@@ -335,7 +291,7 @@
if (preservedWindow != null) {
mDecor = (DecorView) preservedWindow.getDecorView();
mElevation = preservedWindow.getElevation();
- mLoadEleveation = false;
+ mLoadElevation = false;
mForceDecorInstall = true;
// If we're preserving window, carry over the app token from the preserved
// window, as we'll be skipping the addView in handleResumeActivity(), and
@@ -508,15 +464,10 @@
}
}
+ @Override
public void clearContentView() {
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getChildCount() > 1) {
- mNonClientDecorView.removeViewAt(1);
- }
- } else {
- // This window doesn't have non client decor, so we need to just remove the children
- // of the decor view.
- mDecor.removeAllViews();
+ if (mDecor != null) {
+ mDecor.clearContentView();
}
}
@@ -730,15 +681,8 @@
}
}
}
- if (mNonClientDecorView != null) {
- int workspaceId = getWorkspaceId();
- if (mWorkspaceId != workspaceId) {
- mWorkspaceId = workspaceId;
- // We might have to change the kind of surface before we do anything else.
- mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
- StackId.hasWindowShadow(mWorkspaceId));
- mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
- }
+ if (mDecor != null) {
+ mDecor.onConfigurationChanged();
}
}
@@ -1170,7 +1114,7 @@
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 +2205,7 @@
* 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 +2276,6 @@
}
}
- 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
@@ -4065,7 +2485,7 @@
+ Integer.toHexString(mFrameResource));
}
}
- if (mLoadEleveation) {
+ if (mLoadElevation) {
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
}
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
@@ -4135,19 +2555,7 @@
}
mDecor.startChanging();
-
- mNonClientDecorView = createNonClientDecorView();
- View in = mLayoutInflater.inflate(layoutResource, null);
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getParent() == null) {
- decor.addView(mNonClientDecorView,
- new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- mNonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- } else {
- decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- mContentRoot = (ViewGroup) in;
+ final View in = mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
@@ -4202,50 +2610,6 @@
return contentParent;
}
- // Free floating overlapping windows require a non client decor with a caption and shadow..
- private NonClientDecorView createNonClientDecorView() {
- NonClientDecorView nonClientDecorView = null;
- for (int i = mDecor.getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
- View view = mDecor.getChildAt(i);
- if (view instanceof NonClientDecorView) {
- // The decor was most likely saved from a relaunch - so reuse it.
- nonClientDecorView = (NonClientDecorView) view;
- mDecor.removeViewAt(i);
- }
- }
- final WindowManager.LayoutParams attrs = getAttributes();
- boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
- attrs.type == TYPE_APPLICATION;
- mWorkspaceId = getWorkspaceId();
- // Only a non floating application window on one of the allowed workspaces can get a non
- // client decor.
- if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
- // Dependent on the brightness of the used title we either use the
- // dark or the light button frame.
- if (nonClientDecorView == null) {
- Context context = mDecor.getContext();
- TypedValue value = new TypedValue();
- context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
- LayoutInflater inflater = mLayoutInflater.from(context);
- if (Color.luminance(value.data) < 0.5) {
- nonClientDecorView = (NonClientDecorView) inflater.inflate(
- R.layout.non_client_decor_dark, null);
- } else {
- nonClientDecorView = (NonClientDecorView) inflater.inflate(
- R.layout.non_client_decor_light, null);
- }
- }
- nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
- StackId.hasWindowShadow(mWorkspaceId), getResizingBackgroundDrawable(),
- mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
- }
- // Tell the decor if it has a visible non client decor.
- mDecor.enableNonClientDecor(
- nonClientDecorView != null&& StackId.hasWindowDecor(mWorkspaceId));
-
- return nonClientDecorView;
- }
-
/** @hide */
public void alwaysReadCloseOnTouchAttr() {
mAlwaysReadCloseOnTouchAttr = true;
@@ -4450,7 +2814,7 @@
* 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 +3278,7 @@
int curAlpha = 255;
}
- private static final class PanelFeatureState {
+ static final class PanelFeatureState {
/** Feature ID for this panel. */
int featureId;
@@ -5316,30 +3680,12 @@
}
}
- private static class ColorViewState {
- View view = null;
- int targetVisibility = View.INVISIBLE;
- boolean present = false;
+ int getLocalFeaturesPrivate() {
+ return super.getLocalFeatures();
+ }
- 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;
- }
+ protected void setDefaultWindowFormat(int format) {
+ super.setDefaultWindowFormat(format);
}
void sendCloseSystemWindows() {
@@ -5391,28 +3737,6 @@
mIsStartingWindow = isStartingWindow;
}
- /**
- * Returns the Id of the workspace which contains this window.
- * Note that if no workspace can be determined - which usually means that it was not
- * created for an activity - the fullscreen workspace ID will be returned.
- * @return Returns the workspace stack id which contains this window.
- **/
- private int getWorkspaceId() {
- int workspaceId = INVALID_STACK_ID;
- WindowControllerCallback callback = getWindowControllerCallback();
- if (callback != null) {
- try {
- workspaceId = callback.getWindowStackId();
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
- }
- }
- if (workspaceId == INVALID_STACK_ID) {
- return FULLSCREEN_WORKSPACE_STACK_ID;
- }
- return workspaceId;
- }
-
@Override
public void setTheme(int resid) {
mTheme = resid;
@@ -5423,31 +3747,4 @@
}
}
}
-
- /**
- * Returns the color used to fill areas the app has not rendered content to yet when the user
- * is resizing the window of an activity in multi-window mode.
- * */
- private Drawable getResizingBackgroundDrawable() {
- final Context context = mDecor.getContext();
-
- if (mBackgroundResource != 0) {
- final Drawable drawable = context.getDrawable(mBackgroundResource);
- if (drawable != null) {
- return drawable;
- }
- }
-
- if (mBackgroundFallbackResource != 0) {
- final Drawable fallbackDrawable = context.getDrawable(mBackgroundFallbackResource);
- if (fallbackDrawable != null) {
- return fallbackDrawable;
- }
- }
-
- // We shouldn't really get here as the background fallback should be always available since
- // it is defaulted by the system.
- Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + this);
- return null;
- }
}
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index de6e228..48420da 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -21,13 +21,9 @@
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.AttributeSet;
-import android.view.Choreographer;
-import android.view.DisplayListCanvas;
import android.view.MotionEvent;
-import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.View;
import android.widget.LinearLayout;
@@ -39,6 +35,7 @@
import android.util.TypedValue;
import com.android.internal.R;
+import com.android.internal.policy.BackdropFrameRenderer;
import com.android.internal.policy.PhoneWindow;
/**
@@ -75,7 +72,7 @@
private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
private PhoneWindow mOwner = null;
private boolean mWindowHasShadow = false;
- private boolean mShowDecor = false;
+ public boolean mShowDecor = false;
// True when this object is listening for window size changes.
private boolean mAttachedCallbacksToRootViewImpl = false;
@@ -96,11 +93,10 @@
// to max until the first layout command has been executed.
private boolean mAllowUpdateElevation = false;
- // The resize frame renderer.
- private ResizeFrameThread mFrameRendererThread = null;
+ private BackdropFrameRenderer mBackdropFrameRenderer = null;
- private Drawable mResizingBackgroundDrawable;
- private Drawable mCaptionBackgroundDrawable;
+ public Drawable mResizingBackgroundDrawable;
+ public Drawable mCaptionBackgroundDrawable;
public NonClientDecorView(Context context) {
super(context);
@@ -122,10 +118,10 @@
// Note that our ViewRootImpl object will not change.
getViewRootImpl().addWindowCallbacks(this);
mAttachedCallbacksToRootViewImpl = true;
- } else if (mFrameRendererThread != null) {
+ } else if (mBackdropFrameRenderer != null) {
// We are resizing and this call happened due to a configuration change. Tell the
// renderer about it.
- mFrameRendererThread.onConfigurationChange();
+ mBackdropFrameRenderer.onConfigurationChange();
}
}
@@ -208,7 +204,7 @@
* @param showDecor True if the decor should be shown.
* @param windowHasShadow True when the window should show a shadow.
**/
- public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
+ public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
mShowDecor = showDecor;
updateCaptionVisibility();
if (windowHasShadow != mWindowHasShadow) {
@@ -298,7 +294,7 @@
float elevation = 0;
// Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
// is bound to the content size and not the target size.
- if (mWindowHasShadow && mFrameRendererThread == null) {
+ if (mWindowHasShadow && mBackdropFrameRenderer == null) {
boolean fill = isFillingScreen();
elevation = fill ? 0 :
(mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
@@ -347,13 +343,13 @@
releaseResources();
return;
}
- if (mFrameRendererThread != null) {
+ if (mBackdropFrameRenderer != null) {
return;
}
final ThreadedRenderer renderer =
(ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
if (renderer != null) {
- mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
+ mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds);
// Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
// If we want to get the shadow shown while resizing, we would need to elevate a new
// element which owns the caption and has the elevation.
@@ -363,16 +359,16 @@
@Override
public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
- if (mFrameRendererThread == null) {
+ if (mBackdropFrameRenderer == null) {
return false;
}
- return mFrameRendererThread.onContentDrawn(xOffset, yOffset, xSize, ySize);
+ return mBackdropFrameRenderer.onContentDrawn(xOffset, yOffset, xSize, ySize);
}
@Override
public void onRequestDraw(boolean reportNextDraw) {
- if (mFrameRendererThread != null) {
- mFrameRendererThread.onRequestDraw(reportNextDraw);
+ if (mBackdropFrameRenderer != null) {
+ mBackdropFrameRenderer.onRequestDraw(reportNextDraw);
} else if (reportNextDraw) {
// If render thread is gone, just report immediately.
if (isAttachedToWindow()) {
@@ -388,8 +384,8 @@
@Override
public void onWindowSizeIsChanging(Rect newBounds) {
- if (mFrameRendererThread != null) {
- mFrameRendererThread.setTargetRect(newBounds);
+ if (mBackdropFrameRenderer != null) {
+ mBackdropFrameRenderer.setTargetRect(newBounds);
}
}
@@ -397,9 +393,9 @@
* Release the renderer thread which is usually done when the user stops resizing.
*/
private void releaseThreadedRenderer() {
- if (mFrameRendererThread != null) {
- mFrameRendererThread.releaseRenderer();
- mFrameRendererThread = null;
+ if (mBackdropFrameRenderer != null) {
+ mBackdropFrameRenderer.releaseRenderer();
+ mBackdropFrameRenderer = null;
// Bring the shadow back.
updateElevation();
}
@@ -413,256 +409,4 @@
releaseThreadedRenderer();
}
- /**
- * The thread which draws the chrome while we are resizing.
- * It starts with the creation and it ends once someone calls destroy().
- * Any size changes can be passed by a call to setTargetRect will passed to the thread and
- * executed via the Choreographer.
- * TODO(b/24810450): Separate functionality from non-client-decor so that it can be used
- * independently.
- */
- private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
- // This is containing the last requested size by a resize command. Note that this size might
- // or might not have been applied to the output already.
- private final Rect mTargetRect = new Rect();
-
- // The render nodes for the multi threaded renderer.
- private ThreadedRenderer mRenderer;
- private RenderNode mFrameAndBackdropNode;
-
- private final Rect mOldTargetRect = new Rect();
- private final Rect mNewTargetRect = new Rect();
- private Choreographer mChoreographer;
-
- // Cached size values from the last render for the case that the view hierarchy is gone
- // during a configuration change.
- private int mLastContentWidth;
- private int mLastContentHeight;
- private int mLastCaptionHeight;
- private int mLastXOffset;
- private int mLastYOffset;
-
- // Whether to report when next frame is drawn or not.
- private boolean mReportNextDraw;
-
- ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
- setName("ResizeFrame");
- mRenderer = renderer;
-
- // Create a render node for the content and frame backdrop
- // which can be resized independently from the content.
- mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
-
- mRenderer.addRenderNode(mFrameAndBackdropNode, true);
-
- // Set the initial bounds and draw once so that we do not get a broken frame.
- mTargetRect.set(initialBounds);
- synchronized (this) {
- changeWindowSizeLocked(initialBounds);
- }
-
- // Kick off our draw thread.
- start();
- }
-
- /**
- * Call this function asynchronously when the window size has been changed. The change will
- * be picked up once per frame and the frame will be re-rendered accordingly.
- * @param newTargetBounds The new target bounds.
- */
- public void setTargetRect(Rect newTargetBounds) {
- synchronized (this) {
- mTargetRect.set(newTargetBounds);
- // Notify of a bounds change.
- pingRenderLocked();
- }
- }
-
- /**
- * The window got replaced due to a configuration change.
- */
- public void onConfigurationChange() {
- synchronized (this) {
- if (mRenderer != null) {
- // Enforce a window redraw.
- mOldTargetRect.set(0, 0, 0, 0);
- pingRenderLocked();
- }
- }
- }
-
- /**
- * All resources of the renderer will be released. This function can be called from the
- * the UI thread as well as the renderer thread.
- */
- public void releaseRenderer() {
- synchronized (this) {
- if (mRenderer != null) {
- // Invalidate the current content bounds.
- mRenderer.setContentDrawBounds(0, 0, 0, 0);
-
- // Remove the render node again
- // (see comment above - better to do that only once).
- mRenderer.removeRenderNode(mFrameAndBackdropNode);
-
- mRenderer = null;
-
- // Exit the renderer loop.
- pingRenderLocked();
- }
- }
- }
-
- @Override
- public void run() {
- try {
- Looper.prepare();
- synchronized (this) {
- mChoreographer = Choreographer.getInstance();
-
- // Draw at least once.
- mChoreographer.postFrameCallback(this);
- }
- Looper.loop();
- } finally {
- releaseRenderer();
- }
- synchronized (this) {
- // Make sure no more messages are being sent.
- mChoreographer = null;
- }
- }
-
- /**
- * The implementation of the FrameCallback.
- * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
- * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
- */
- @Override
- public void doFrame(long frameTimeNanos) {
- synchronized (this) {
- if (mRenderer == null) {
- reportDrawIfNeeded();
- // Tell the looper to stop. We are done.
- Looper.myLooper().quit();
- return;
- }
- mNewTargetRect.set(mTargetRect);
- if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
- mOldTargetRect.set(mNewTargetRect);
- changeWindowSizeLocked(mNewTargetRect);
- }
- }
- }
-
- /**
- * The content is about to be drawn and we got the location of where it will be shown.
- * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
- * if the previous call was ignored since the size was unknown.
- * @param xOffset The x offset where the content is drawn to.
- * @param yOffset The y offset where the content is drawn to.
- * @param xSize The width size of the content. This should not be 0.
- * @param ySize The height of the content.
- * @return true if a frame should be requested after the content is drawn; false otherwise.
- */
- public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
- synchronized (this) {
- final boolean firstCall = mLastContentWidth == 0;
- // The current content buffer is drawn here.
- mLastContentWidth = xSize;
- mLastContentHeight = ySize - mLastCaptionHeight;
- mLastXOffset = xOffset;
- mLastYOffset = yOffset;
-
- mRenderer.setContentDrawBounds(
- mLastXOffset,
- mLastYOffset,
- mLastXOffset + mLastContentWidth,
- mLastYOffset + mLastCaptionHeight + mLastContentHeight);
- // If this was the first call and changeWindowSizeLocked got already called prior
- // to us, we should re-issue a changeWindowSizeLocked now.
- return firstCall && (mLastCaptionHeight != 0 || !mShowDecor);
- }
- }
-
- public void onRequestDraw(boolean reportNextDraw) {
- synchronized (this) {
- mReportNextDraw = reportNextDraw;
- mOldTargetRect.set(0, 0, 0, 0);
- pingRenderLocked();
- }
- }
-
- /**
- * Resizing the frame to fit the new window size.
- * @param newBounds The window bounds which needs to be drawn.
- */
- private void changeWindowSizeLocked(Rect newBounds) {
- // While a configuration change is taking place the view hierarchy might become
- // inaccessible. For that case we remember the previous metrics to avoid flashes.
- // Note that even when there is no visible caption, the caption child will exist.
- View caption = getChildAt(0);
- if (caption != null) {
- final int captionHeight = caption.getHeight();
- // The caption height will probably never dynamically change while we are resizing.
- // Once set to something other then 0 it should be kept that way.
- if (captionHeight != 0) {
- // Remember the height of the caption.
- mLastCaptionHeight = captionHeight;
- }
- }
- // Make sure that the other thread has already prepared the render draw calls for the
- // content. If any size is 0, we have to wait for it to be drawn first.
- if ((mLastCaptionHeight == 0 && mShowDecor) ||
- mLastContentWidth == 0 || mLastContentHeight == 0) {
- return;
- }
- // Since the surface is spanning the entire screen, we have to add the start offset of
- // the bounds to get to the surface location.
- final int left = mLastXOffset + newBounds.left;
- final int top = mLastYOffset + newBounds.top;
- final int width = newBounds.width();
- final int height = newBounds.height();
-
- mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
-
- // Draw the caption and content backdrops in to our render node.
- DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
- mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
- mCaptionBackgroundDrawable.draw(canvas);
-
- // The backdrop: clear everything with the background. Clipping is done elsewhere.
- mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
- mResizingBackgroundDrawable.draw(canvas);
- mFrameAndBackdropNode.end(canvas);
-
- // We need to render the node explicitly
- mRenderer.drawRenderNode(mFrameAndBackdropNode);
-
- reportDrawIfNeeded();
- }
-
- /**
- * Notify view root that a frame has been drawn by us, if it has requested so.
- */
- private void reportDrawIfNeeded() {
- if (mReportNextDraw) {
- if (isAttachedToWindow()) {
- getViewRootImpl().reportDrawFinish();
- }
- mReportNextDraw = false;
- }
- }
-
- /**
- * Sends a message to the renderer to wake up and perform the next action which can be
- * either the next rendering or the self destruction if mRenderer is null.
- * Note: This call must be synchronized.
- */
- private void pingRenderLocked() {
- if (mChoreographer != null) {
- mChoreographer.postFrameCallback(this);
- }
- }
- }
}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 703a9bd..a805b6d 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1006,6 +1006,7 @@
// 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/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 50cf302..90fc22b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3242,6 +3242,14 @@
</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 54e43c8..b6b2e20 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 faa76f2..00c0fe8 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 6820c25..e8f6b46 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 e22dc4a..21e3057 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 @@
<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/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4acad67..8565372 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -206,7 +206,9 @@
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 1aa291f..d2d3285 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -35,11 +35,11 @@
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 @@
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 @@
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 @@
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 @@
mRenderTarget.offscreenBuffer->region.orSelf(dirty);
}
mRenderState.render(glop, mRenderTarget.orthoMatrix);
- mHasDrawn = true;
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
////////////////////////////////////////////////////////////////////////////////
@@ -217,7 +226,7 @@
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 @@
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 @@
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 d6d9cb1..29f9a6f 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -39,27 +39,39 @@
*/
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 @@
uint32_t viewportHeight = 0;
Matrix4 orthoMatrix;
} mRenderTarget;
+
+ const LightInfo mLightInfo;
};
/**
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index a327614..94a11f1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -79,7 +79,6 @@
}
void Caches::initExtensions() {
- mExtensions.load();
if (mExtensions.hasDebugMarker()) {
eventMark = glInsertEventMarkerEXT;
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 03b1706..39b7ecb 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -40,7 +40,6 @@
}
void DeviceInfo::load() {
- mExtensions.load();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
}
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index e257715..02caaa4 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,7 +35,7 @@
#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 8ccfabd..67cc747 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -31,7 +31,7 @@
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 80efaed..b04f16f 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 @@
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 @@
};
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 @@
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 @@
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(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 @@
}
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 @@
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::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 2c30f0d..09d5cbc 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -67,13 +67,13 @@
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 @@
const uint32_t width;
const uint32_t height;
+ const Rect repaintRect;
OffscreenBuffer* offscreenBuffer;
const BeginLayerOp* beginLayerOp;
const RenderNode* renderNode;
@@ -116,14 +117,15 @@
// 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 @@
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 @@
}
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 @@
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 bb7a0a7c..ef05367 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 @@
* 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 @@
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 e988555..6ab253c 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@
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 e177f9a..2713f46 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -326,15 +326,11 @@
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 0a58f4b..2f535bb 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@
///////////////////////////////////////////////////////////////////////////////
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 @@
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 7b8d0e5..b24858e 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,7 +48,7 @@
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 @@
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 f0fd82d..fac6c35 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -34,6 +34,11 @@
* 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 @@
// 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 @@
// 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 f094b2d..89cadea 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 @@
// 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 @@
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 @@
// 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 d656014..c3cfc94 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 @@
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 07080a2..a8c9bba 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -29,6 +29,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -198,13 +206,8 @@
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 @@
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 @@
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -305,7 +305,7 @@
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 @@
canvas.restore();
});
- OpReorderer reorderer(800, 800, *dl);
+ OpReorderer reorderer(800, 800, *dl, sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -363,19 +363,21 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
canvas.drawRenderNode(childPtr);
canvas.restore();
}, TestUtils::getHwLayerSetupCallback());
- *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+ OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+ *(parent->getLayerHandle()) = &parentLayer;
- TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+ auto syncedList = createSyncedNodeList(parent);
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(parent.get());
-
- 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 @@
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,
- [&caster] (RecordingCanvas& canvas) {
+ [] (RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(caster.get());
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
- 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);
ShadowTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex());
}
-static void testProperty(
- TestUtils::PropSetupCallback propSetupCallback,
+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,
+ [] (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(createWhiteRectShadowCaster(5.0f).get());
+ canvas.insertReorderBarrier(false);
+ canvas.restoreToCount(count);
+ });
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+
+ ShadowSaveLayerTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(5, renderer.getIndex());
+}
+
+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++);
+ }
+ };
+
+ 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(5, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
+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 @@
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 efa28ae..38bafd5 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -45,6 +45,20 @@
&& 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/SoundPool.java b/media/java/android/media/SoundPool.java
index 1355635..3164930 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -166,7 +166,7 @@
updateAppOpsPlayAudio();
// register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
mAppOpsCallback = new IAppOpsCallback.Stub() {
- public void opChanged(int op, String packageName) {
+ public void opChanged(int op, int uid, String packageName) {
synchronized (mLock) {
if (op == AppOpsManager.OP_PLAY_AUDIO) {
updateAppOpsPlayAudio();
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 043b80e..6197c70 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -931,9 +931,7 @@
*
* @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 6f1a89b..369ab7d 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.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.TimeUnit;
import java.util.concurrent.TimeoutException;
+@MediumTest
public class CopyTest extends ServiceTestCase<CopyService> {
public CopyTest() {
@@ -89,9 +92,6 @@
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 @@
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 9060516..ba91c83 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.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 746e2117..b250e5d 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.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 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 d1ce564..b3d45ae 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 @@
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.List;
import java.util.Set;
+@SmallTest
public class MultiSelectManagerTest extends AndroidTestCase {
private static final List<String> items;
@@ -163,7 +165,6 @@
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 c4b6ce5..c856b22 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.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 64da750..72fc108 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 0d4265a..1c96906 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,7 @@
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.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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 ae29f52..4238721e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -23,6 +23,7 @@
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 @@
}
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 3151ccb..3dc69cc 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.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 @@
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 @@
*/
@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 @@
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 977b12e..97c1d29 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -26,6 +26,9 @@
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 7328f05..9c5d6b6 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.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 @@
*/
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 @@
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 @@
}
/**
+ * 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 @@
* {@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 @@
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 0931445..f0f8161 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -78,7 +78,7 @@
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 @@
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 @@
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 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 415f89e..d9ed4ab 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -2,6 +2,7 @@
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 @@
}
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 28ad3f4..e1307e9 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 a012d7f..a80eb51 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.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 @@
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 ce4cf14..25dd1c8 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 @@
};
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 @@
}
{
- 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 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 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 @@
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 @@
}
{
- 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 @@
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 @@
}
{
- 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 @@
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 @@
}
{
- 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 @@
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 @@
}
{
- 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 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 @@
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 @@
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 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 @@
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 @@
}
{
- 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 @@
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 @@
}
{
- 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 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 @@
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 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 @@
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 @@
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 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 @@
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 @@
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 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 @@
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 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 bc7f28c..82e08cd 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 @@
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 @@
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 @@
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 @@
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 @@
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 53018cc..7c947f5 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 @@
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 3d92cc2..3833799 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 @@
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 a243375..0fb0f34 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 @@
@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 0000000..1442b1b
--- /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 991fa38b..0000000
--- 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 0000000..11fef2d
--- /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 8da5769..15cc139 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 70abdf4..6d81788 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 f409fd4..e758835 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.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 @@
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 @@
private Spinner mDestinationSpinner;
private DestinationAdapter mDestinationSpinnerAdapter;
+ private boolean mShowDestinationPrompt;
private Spinner mMediaSizeSpinner;
private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -1093,6 +1097,7 @@
updateOptionsUi();
addCurrentPrinterToHistory();
+ setUserPrinted();
PageRange[] selectedPages = computeSelectedPages();
if (!Arrays.equals(mSelectedPages, selectedPages)) {
@@ -1195,6 +1200,29 @@
// 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 @@
&& 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 @@
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 @@
mCopiesEditText.setText(MIN_COPIES_STRING);
mCopiesEditText.requestFocus();
}
+
+ if (mShowDestinationPrompt) {
+ disableOptionsUi();
+ }
}
private void updateSummary() {
@@ -1980,6 +2010,32 @@
}
}
+
+ /**
+ * 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 @@
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 @@
@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 9e48849..a37196e 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 0000000..ff1c866
--- /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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d5f9557e..00b10a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1067,6 +1067,9 @@
<!-- Name of status bar -->
<string name="status_bar">Status bar</string>
+ <!-- Name of overview -->
+ <string name="overview">Overview</string>
+
<!-- Name of demo mode (mode with preset icons for screenshots) -->
<string name="demo_mode">Demo mode</string>
@@ -1156,6 +1159,11 @@
<!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
<string name="qs_paging" translatable="false">Use the new Quick Settings</string>
+ <!-- Toggles fast-toggling recents via the recents button -->
+ <string name="overview_fast_toggle_via_button">Enable fast toggle</string>
+ <!-- Description for the toggle for fast-toggling recents via the recents button -->
+ <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+
<!-- Category in the System UI Tuner settings, where new/experimental
settings are -->
<string name="experimental">Experimental</string>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 5980108..c36cab8 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -87,6 +87,17 @@
</PreferenceScreen>
+
+ <PreferenceScreen
+ android:title="@string/overview" >
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="overview_fast_toggle"
+ android:title="@string/overview_fast_toggle_via_button"
+ android:summary="@string/overview_fast_toggle_via_button_desc" />
+
+ </PreferenceScreen>
+
<SwitchPreference
android:key="battery_pct"
android:title="@string/show_battery_percentage"
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 3657cf2..9d98772 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -30,7 +30,8 @@
@Retention(RetentionPolicy.SOURCE)
@StringDef({
- Key.SEARCH_APP_WIDGET_ID,
+ Key.OVERVIEW_SEARCH_APP_WIDGET_ID,
+ Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
Key.DEBUG_MODE_ENABLED,
Key.HOTSPOT_TILE_LAST_USED,
Key.COLOR_INVERSION_TILE_LAST_USED,
@@ -43,8 +44,8 @@
Key.DND_FAVORITE_ZEN,
})
public @interface Key {
- String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
- String SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
+ String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+ String OVERVIEW_SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
String DEBUG_MODE_ENABLED = "debugModeEnabled";
String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a429447..c08fb05 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,34 +29,4 @@
public static final int DismissSourceHeaderButton = 2;
}
- // TODO: Move into RecentsDebugFlags
- public static class DebugFlags {
-
- public static class App {
- // Enables debug drawing for the transition thumbnail
- public static final boolean EnableTransitionThumbnailDebugMode = false;
- // Enables the filtering of tasks according to their grouping
- public static final boolean EnableTaskFiltering = false;
- // Enables dismiss-all
- public static final boolean EnableDismissAll = false;
- // Enables fast-toggling by just tapping on the recents button
- public static final boolean EnableFastToggleRecents = false;
- // Enables the thumbnail alpha on the front-most task
- public static final boolean EnableThumbnailAlphaOnFrontmost = false;
- // This disables the search bar integration
- public static final boolean DisableSearchBar = true;
- // This disables the bitmap and icon caches
- public static final boolean DisableBackgroundCache = false;
- // Enables the simulated task affiliations
- public static final boolean EnableSimulatedTaskGroups = false;
- // Defines the number of mock task affiliations per group
- public static final int TaskAffiliationsGroupCount = 12;
- // Enables us to create mock recents tasks
- public static final boolean EnableSystemServicesProxy = false;
- // Defines the number of mock recents packages to create
- public static final int SystemServicesProxyMockPackageCount = 3;
- // Defines the number of mock recents tasks to create
- public static final int SystemServicesProxyMockTaskCount = 100;
- }
- }
}
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 0000000..8ae8c53
--- /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/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 95f1eb2..3806b46 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -62,6 +62,7 @@
private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
private static SystemServicesProxy sSystemServicesProxy;
+ private static RecentsDebugFlags sDebugFlags;
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
@@ -148,8 +149,13 @@
return sConfiguration;
}
+ public static RecentsDebugFlags getDebugFlags() {
+ return sDebugFlags;
+ }
+
@Override
public void start() {
+ sDebugFlags = new RecentsDebugFlags(mContext);
sSystemServicesProxy = new SystemServicesProxy(mContext);
sTaskLoader = new RecentsTaskLoader(mContext);
sConfiguration = new RecentsConfiguration(mContext);
@@ -166,6 +172,7 @@
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+ EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
// Due to the fact that RecentsActivity is per-user, we need to establish and interface for
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3ae8827..b4bb392 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.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;
@@ -44,6 +45,7 @@
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -216,14 +218,14 @@
mEmptyView = mEmptyViewStub.inflate();
}
mEmptyView.setVisibility(View.VISIBLE);
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
mRecentsView.setSearchBarVisibility(View.GONE);
}
} else {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
if (mRecentsView.hasValidSearchBar()) {
mRecentsView.setSearchBarVisibility(View.VISIBLE);
} else {
@@ -337,7 +339,7 @@
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the widget host (the host id is static and does not change)
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
}
mPackageMonitor = new RecentsPackageMonitor();
@@ -352,6 +354,8 @@
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);
@@ -361,14 +365,14 @@
mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
// Bind the search app widget when we first start up
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
}
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
}
registerReceiver(mSystemBroadcastReceiver, filter);
@@ -430,12 +434,8 @@
protected void onPause() {
super.onPause();
- if (Constants.DebugFlags.App.EnableFastToggleRecents) {
- // Stop the fast-toggle dozer
- mIterateTrigger.stopDozing();
- }
-
- if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled()) {
// Stop the fast-toggle dozer
mIterateTrigger.stopDozing();
}
@@ -480,7 +480,7 @@
mPackageMonitor.unregister();
// Stop listening for widget package changes if there was one bound
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
mAppWidgetHost.stopListening();
}
@@ -614,7 +614,7 @@
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
ctx.postAnimationTrigger.increment();
if (mSearchWidgetInfo != null) {
- if (!Constants.DebugFlags.App.DisableSearchBar) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar) {
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
@@ -633,8 +633,8 @@
// the dozer now
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- if (Constants.DebugFlags.App.EnableFastToggleRecents &&
- !launchState.launchedWithAltTab) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
mIterateTrigger.startDozing();
}
}
@@ -648,6 +648,11 @@
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;
@@ -717,6 +722,11 @@
MetricsLogger.count(this, "overview_screen_pinned", 1);
}
+ public final void onBusEvent(DebugFlagsChangedEvent event) {
+ // Just finish recents so that we can reload the flags anew on the next instantiation
+ finish();
+ }
+
private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 01ffd2a..7f7dbce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -74,7 +74,8 @@
* Returns the task to focus given the current launch state.
*/
public int getInitialFocusTaskIndex(int numTasks) {
- if (Constants.DebugFlags.App.EnableFastToggleRecents && !launchedWithAltTab) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
// If we are fast toggling, then focus the next task depending on when you are on home
// or coming in from another app
if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index a73f323..8f952be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -154,7 +154,7 @@
int swInset = getInsetToSmallestWidth(windowBounds.right - windowBounds.left);
int top = searchBarBounds.isEmpty() ? topInset : 0;
taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom + top,
- windowBounds.right - swInset, windowBounds.bottom);
+ windowBounds.right - swInset - rightInset, windowBounds.bottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
new file mode 100644
index 0000000..6c74a4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
+import com.android.systemui.tuner.TunerService;
+
+/**
+ * Tunable debug flags
+ */
+public class RecentsDebugFlags implements TunerService.Tunable {
+
+ private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+
+ public static class Static {
+ // Enables debug drawing for the transition thumbnail
+ public static final boolean EnableTransitionThumbnailDebugMode = false;
+ // This disables the search bar integration
+ public static final boolean DisableSearchBar = true;
+ // This disables the bitmap and icon caches
+ public static final boolean DisableBackgroundCache = false;
+ // Enables the simulated task affiliations
+ public static final boolean EnableSimulatedTaskGroups = false;
+ // Defines the number of mock task affiliations per group
+ public static final int TaskAffiliationsGroupCount = 12;
+ // Enables us to create mock recents tasks
+ public static final boolean EnableSystemServicesProxy = false;
+ // Defines the number of mock recents packages to create
+ public static final int SystemServicesProxyMockPackageCount = 3;
+ // Defines the number of mock recents tasks to create
+ public static final int SystemServicesProxyMockTaskCount = 100;
+ }
+
+ private boolean mForceEnableFreeformWorkspace;
+ private boolean mEnableFastToggleRecents;
+
+ /**
+ * We read the prefs once when we start the activity, then update them as the tuner changes
+ * the flags.
+ */
+ public RecentsDebugFlags(Context context) {
+ // Register all our flags, this will also call onTuningChanged() for each key, which will
+ // initialize the current state of each flag
+ TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+ }
+
+ /**
+ * @return whether we are enabling fast toggling.
+ */
+ public boolean isFastToggleRecentsEnabled() {
+ return mEnableFastToggleRecents;
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ switch (key) {
+ case KEY_FAST_TOGGLE:
+ mEnableFastToggleRecents = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
+ }
+ EventBus.getDefault().send(new DebugFlagsChangedEvent());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 50aa2f7..85a2eda 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 @@
* 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;
@@ -324,8 +324,8 @@
if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- if (Constants.DebugFlags.App.EnableFastToggleRecents &&
- !launchState.launchedWithAltTab) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
// Notify recents to move onto the next task
EventBus.getDefault().post(new IterateRecentsEvent());
} else {
@@ -555,7 +555,7 @@
// Update the configuration for the current state
config.update(mContext, ssp, ssp.getWindowRect());
- if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) {
// Try and pre-emptively bind the search widget on startup to ensure that we
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
@@ -758,7 +758,7 @@
int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
Bitmap.Config.ARGB_8888);
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
thumbnail.eraseColor(0xFFff0000);
} else {
Canvas c = new Canvas(thumbnail);
@@ -816,11 +816,11 @@
if (!useThumbnailTransition) {
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition and do the animation from home
- if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
+ if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) {
SystemServicesProxy ssp = Recents.getSystemServices();
String homeActivityPackage = ssp.getHomeActivityPackageName();
String searchWidgetPackage = Prefs.getString(mContext,
- Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+ Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
// Determine whether we are coming from a search owned home activity
boolean fromSearchHome = (homeActivityPackage != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
new file mode 100644
index 0000000..fe3bf26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * 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.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the SystemUI tuner changes a flag.
+ */
+public class DebugFlagsChangedEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 0432ac9..16d6929 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -63,7 +63,8 @@
import com.android.internal.os.BackgroundThread;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
import java.io.IOException;
@@ -113,6 +114,7 @@
/** Private constructor */
public SystemServicesProxy(Context context) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
mAccm = AccessibilityManager.getInstance(context);
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mIam = ActivityManagerNative.getDefault();
@@ -124,8 +126,8 @@
mUm = UserManager.get(context);
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
- mHasFreeformWorkspaceSupport = false && mPm.hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ mHasFreeformWorkspaceSupport = false &&
+ mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -143,7 +145,7 @@
// Resolve the assist intent
mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
// Create a dummy icon
mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
mDummyIcon.eraseColor(0xFF999999);
@@ -156,13 +158,13 @@
if (mAm == null) return null;
// If we are mocking, then create some recent tasks
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
ArrayList<ActivityManager.RecentTaskInfo> tasks =
new ArrayList<ActivityManager.RecentTaskInfo>();
- int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount);
+ int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.SystemServicesProxyMockTaskCount);
for (int i = 0; i < count; i++) {
// Create a dummy component name
- int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
+ int packageIndex = i % RecentsDebugFlags.Static.SystemServicesProxyMockPackageCount;
ComponentName cn = new ComponentName("com.android.test" + packageIndex,
"com.android.test" + i + ".Activity");
String description = "" + i + " - " +
@@ -381,7 +383,7 @@
if (mAm == null) return null;
// If we are mocking, then just return a dummy thumbnail
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
Bitmap.Config.ARGB_8888);
thumbnail.eraseColor(0xff333333);
@@ -443,7 +445,7 @@
/** Moves a task to the front with the specified activity options. */
public void moveTaskToFront(int taskId, ActivityOptions opts) {
if (mAm == null) return;
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
if (opts != null) {
mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME,
@@ -456,7 +458,7 @@
/** Removes the task */
public void removeTask(final int taskId) {
if (mAm == null) return;
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
// Remove the task.
BackgroundThread.getHandler().post(new Runnable() {
@@ -475,7 +477,7 @@
*/
public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
if (mIpm == null) return null;
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
try {
return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
@@ -492,7 +494,7 @@
*/
public ActivityInfo getActivityInfo(ComponentName cn) {
if (mPm == null) return null;
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
try {
return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
@@ -507,7 +509,7 @@
if (mPm == null) return null;
// If we are mocking, then return a mock label
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return "Recent Task";
}
@@ -519,7 +521,7 @@
if (mPm == null) return null;
// If we are mocking, then return a mock label
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return "Recent Task";
}
@@ -549,7 +551,7 @@
if (mPm == null) return null;
// If we are mocking, then return a mock label
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
return new ColorDrawable(0xFF666666);
}
@@ -580,7 +582,7 @@
/** Returns the package name of the home activity. */
public String getHomeActivityPackageName() {
if (mPm == null) return null;
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null;
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return null;
ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
@@ -623,22 +625,22 @@
* Returns the current search widget id.
*/
public int getSearchAppWidgetId(Context context) {
- return Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+ return Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
}
/**
* Returns the current search widget info, binding a new one if necessary.
*/
public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
- int searchWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+ int searchWidgetId = Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
// Return the search widget info if it hasn't changed
if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
- if (Prefs.getString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null) == null) {
- Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+ if (Prefs.getString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null) == null) {
+ Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
searchWidgetInfo.provider.getPackageName());
}
return searchWidgetInfo;
@@ -654,16 +656,16 @@
Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
resolvedSearchWidgetInfo);
if (widgetInfo != null) {
- Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, widgetInfo.first);
- Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+ Prefs.putInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, widgetInfo.first);
+ Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
widgetInfo.second.provider.getPackageName());
return widgetInfo.second;
}
}
// If we fall through here, then there is no resolved search widget, so clear the state
- Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_ID);
- Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE);
+ Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID);
+ Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE);
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index bba453a..fe67fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -30,9 +30,9 @@
import android.util.Log;
import android.util.LruCache;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -283,9 +283,9 @@
res.getColor(R.color.recents_task_bar_default_background_color);
mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
- int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+ int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
mMaxIconCacheSize;
- int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+ int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
mMaxThumbnailCacheSize;
// Create the default assets
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 6734012..f26dcde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -24,8 +24,8 @@
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.misc.NamedCounter;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
@@ -543,7 +543,7 @@
* Temporary: This method will simulate affiliation groups by
*/
public void createAffiliatedGroupings(Context context) {
- if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
+ if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) {
HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
// Sort all tasks by increasing firstActiveTime of the task
ArrayList<Task> tasks = mTaskList.getTasks();
@@ -559,7 +559,7 @@
String prevPackage = "";
int prevAffiliation = -1;
Random r = new Random();
- int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+ int groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
String packageName = t.key.getComponent().getPackageName();
@@ -574,7 +574,7 @@
addGroup(group);
prevAffiliation = affiliation;
prevPackage = packageName;
- groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+ groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
}
group.addTask(t);
taskMap.put(t.key, t);
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 85b8fcf..4ecb80a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -27,13 +27,14 @@
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.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -106,6 +107,7 @@
// 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 +118,12 @@
} 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) {
@@ -313,7 +320,7 @@
b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
Bitmap.Config.ARGB_8888);
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
b.eraseColor(0xFFff0000);
} else {
Canvas c = new Canvas(b);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 51091c3..b3bd6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -201,7 +201,7 @@
taskStackBounds.right - widthPadding,
taskStackBounds.bottom);
// Anchor the task rect to the top-center of the non-freeform stack rect
- int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
+ int size = mStackRect.width();
mTaskRect.set(mStackRect.left, mStackRect.top,
mStackRect.left + size, mStackRect.top + size);
mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index a57ac9d..7250d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -114,6 +114,7 @@
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
ArrayList<TaskView> mTaskViews = new ArrayList<>();
List<TaskView> mImmutableTaskViews = new ArrayList<>();
+ List<TaskView> mTmpTaskViews = new ArrayList<>();
LayoutInflater mInflater;
boolean mLayersDisabled;
boolean mTouchExplorationEnabled;
@@ -279,14 +280,9 @@
}
// Mark each task view for relayout
- if (mViewPool != null) {
- Iterator<TaskView> iter = mViewPool.poolViewIterator();
- if (iter != null) {
- while (iter.hasNext()) {
- TaskView tv = iter.next();
- tv.reset();
- }
- }
+ List<TaskView> poolViews = mViewPool.getViews();
+ for (TaskView tv : poolViews) {
+ tv.reset();
}
// Reset the stack state
@@ -862,10 +858,12 @@
}
// Measure each of the TaskViews
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
+ mTmpTaskViews.clear();
+ mTmpTaskViews.addAll(getTaskViews());
+ mTmpTaskViews.addAll(mViewPool.getViews());
+ int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
+ TaskView tv = mTmpTaskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -891,10 +889,12 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Layout each of the TaskViews
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
+ mTmpTaskViews.clear();
+ mTmpTaskViews.addAll(getTaskViews());
+ mTmpTaskViews.addAll(mViewPool.getViews());
+ int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
+ TaskView tv = mTmpTaskViews.get(i);
if (tv.getBackground() != null) {
tv.getBackground().getPadding(mTmpRect);
} else {
@@ -911,6 +911,9 @@
}
if (changed) {
+ if (mStackScroller.isScrollOutOfBounds()) {
+ mStackScroller.boundScroll();
+ }
requestSynchronizeStackViewsWithModel();
synchronizeStackViewsWithModel();
clipTaskViews(true /* forceUpdate */);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index cb7465d..2c8f316 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -45,6 +45,7 @@
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -324,22 +325,9 @@
if (launchState.launchedFromAppWithThumbnail) {
if (mTask.isLaunchTarget) {
- // Animate the dim/overlay
- if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
- // Animate the thumbnail alpha before the dim animation (to prevent updating the
- // hardware layer)
- mThumbnailView.startEnterRecentsAnimation(new Runnable() {
- @Override
- public void run() {
- animateDimToProgress(taskViewEnterFromAppDuration,
- ctx.postAnimationTrigger.decrementOnAnimationEnd());
- }
- });
- } else {
- // Immediately start the dim animation
- animateDimToProgress(taskViewEnterFromAppDuration,
- ctx.postAnimationTrigger.decrementOnAnimationEnd());
- }
+ // Immediately start the dim animation
+ animateDimToProgress(taskViewEnterFromAppDuration,
+ ctx.postAnimationTrigger.decrementOnAnimationEnd());
ctx.postAnimationTrigger.increment();
// Animate the action button in
@@ -635,7 +623,9 @@
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- if (Constants.DebugFlags.App.EnableFastToggleRecents && mIsFocused) {
+
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
Paint tmpPaint = new Paint();
Rect tmpRect = new Rect();
tmpRect.set(0, 0, getWidth(), getHeight());
@@ -676,7 +666,8 @@
clearAccessibilityFocus();
}
}
- if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ RecentsDebugFlags flags = Recents.getDebugFlags();
+ if (flags.isFastToggleRecentsEnabled()) {
invalidate();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index 12b91af..31fbd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -20,6 +20,7 @@
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
/* A view pool to manage more views than we can visibly handle */
@@ -76,11 +77,10 @@
return v;
}
- /** Returns an iterator to the list of the views in the pool. */
- Iterator<V> poolViewIterator() {
- if (mPool != null) {
- return mPool.iterator();
- }
- return null;
+ /**
+ * Returns the list of views in the pool.
+ */
+ List<V> getViews() {
+ return mPool;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3f0000e..d3d9bef 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.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 @@
protected boolean mDeviceInteractive;
protected boolean mVisible;
+ protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
@@ -468,7 +470,6 @@
processForRemoteInput(sbn.getNotification());
String key = sbn.getKey();
boolean isUpdate = mNotificationData.get(key) != null;
-
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since we're not going to show them
// anyway. This is true also when the summary is canceled,
@@ -1664,7 +1665,10 @@
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 +1750,8 @@
}
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,9 +2017,12 @@
Entry entry = mNotificationData.get(key);
if (entry == null) {
return;
+ } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
}
Notification n = notification.getNotification();
+ mNotificationData.updateRanking(ranking);
boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
boolean shouldInterrupt = shouldInterrupt(entry, notification);
@@ -2070,7 +2077,6 @@
inflateViews(entry, mStackScroller);
}
updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
- mNotificationData.updateRanking(ranking);
updateNotifications();
// Update the veto button accordingly (and as a result, whether this row is
@@ -2160,7 +2166,6 @@
boolean accessibilityForcesLaunch = isFullscreen
&& mAccessibilityManager.isTouchExplorationEnabled();
boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
-
boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
&& isAllowed
&& !accessibilityForcesLaunch
@@ -2168,7 +2173,8 @@
&& mPowerManager.isScreenOn()
&& (!mStatusBarKeyguardViewManager.isShowing()
|| mStatusBarKeyguardViewManager.isOccluded())
- && !mStatusBarKeyguardViewManager.isInputRestricted();
+ && !mStatusBarKeyguardViewManager.isInputRestricted()
+ && !mNotificationData.shouldSuppressPeek(sbn.getKey());
try {
interrupt = interrupt && !mDreamManager.isDreaming();
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 6a90d8e..4328e24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -292,6 +292,15 @@
return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
}
+ public boolean shouldSuppressPeek(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return (mTmpRanking.getSuppressedVisualEffects()
+ & NotificationListenerService.SUPPRESSED_EFFECT_PEEK) != 0;
+ }
+ return false;
+ }
+
private void updateRankingAndSort(RankingMap ranking) {
if (ranking != null) {
mRankingMap = ranking;
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 bbef1c0..fbe9730 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 @@
// 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 @@
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 @@
* @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 3e52515..0cddf1d 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 @@
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
= new HashMap<>();
- private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
@@ -1262,6 +1261,7 @@
Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
+ mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
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 dc9f5e8..5cfd174 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 @@
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 @@
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 b52687a..5f6cbf9 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.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 @@
private TouchExplorer mTouchExplorer;
- private ScreenMagnifier mScreenMagnifier;
+ private MagnificationGestureHandler mMagnificationGestureHandler;
private AutoclickController mAutoclickController;
@@ -363,14 +362,13 @@
}
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 @@
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 535a8ef..9f1dc0a 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.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 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 @@
private MagnificationController mMagnificationController;
+ private boolean mUnregisterMagnificationOnReset;
+
private InteractionBridge mInteractionBridge;
private AlertDialog mEnableTouchExplorationDialog;
@@ -762,6 +766,28 @@
}
/**
+ * 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 @@
}
}
+ 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 @@
}
}
+ /**
+ * 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 @@
updateTouchExplorationLocked(userState);
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
+ updateMagnificationLocked(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1663,6 +1704,44 @@
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 @@
}
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 @@
}
@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 @@
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 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 @@
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 @@
& 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 781d134..a093d92 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 @@
* 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 final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+ 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 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 @@
}
/**
- * 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);
+ public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) {
+ synchronized (mLock) {
+ mMagnifiedRegion.set(magnified);
+ mMagnifiedRegion.getBounds(mMagnifiedBounds);
+ mAvailableRegion.set(available);
- 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();
+ 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 @@
* 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 @@
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 @@
}
/**
+ * 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 @@
* @return the scale currently used by the window manager
*/
public float getSentScale() {
- return mSentMagnificationSpec.scale;
+ return mSpecAnimationBridge.mSentMagnificationSpec.scale;
}
/**
@@ -176,7 +291,7 @@
* @return the X offset currently used by the window manager
*/
public float getSentOffsetX() {
- return mSentMagnificationSpec.offsetX;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
}
/**
@@ -187,7 +302,7 @@
* @return the Y offset currently used by the window manager
*/
public float getSentOffsetY() {
- return mSentMagnificationSpec.offsetY;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
/**
@@ -196,21 +311,24 @@
*
* @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 @@
* 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 @@
* 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 @@
* 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;
+ public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) {
+ synchronized (mLock) {
+ return setScaleAndCenterLocked(scale, centerX, centerY, animate);
}
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
- }
- if (DEBUG_MAGNIFICATION_CONTROLLER) {
- Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
- }
- updateMagnificationSpec(scale, centerX, centerY);
- if (animate) {
- animateMagnificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
- mAms.onMagnificationStateChanged();
+ }
+
+ 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 @@
* @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);
+ 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);
+ }
}
- 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);
+ /**
+ * 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();
}
- private float getMinOffsetX() {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportWidth = magnifiedFrame.width();
+ /**
+ * 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;
- public void setMagnificationSpec(MagnificationSpec spec) {
- if (DEBUG_SET_MAGNIFICATION_SPEC) {
- Slog.i(LOG_TAG, "Sending: " + spec);
+ synchronized (mLock) {
+ if (isMagnifying()) {
+ reset(false);
+ }
+ }
}
- mSentMagnificationSpec.scale = spec.scale;
- mSentMagnificationSpec.offsetX = spec.offsetX;
- mSentMagnificationSpec.offsetY = spec.offsetY;
- mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
}
- public MagnificationSpec getMagnificationSpec() {
- return mSentMagnificationSpec;
+ private boolean isScreenMagnificationAutoUpdateEnabled() {
+ return (Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+ DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
}
- private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
- private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+ /**
+ * 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;
+ }
+ }
+ }
+
+ 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 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 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 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
similarity index 65%
rename from services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
rename to services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 8feb167..51c8ab5 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.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 @@
*
* 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 @@
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 float MIN_SCALE = 2.0f;
+ private static final float MAX_SCALE = 5.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 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 @@
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 @@
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
+ mNext.onKeyEvent(event, policyFlags);
}
}
@@ -366,15 +184,13 @@
@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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
};
+ @Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
cacheDelayedMotionEvent(event, rawEvent, policyFlags);
final int action = event.getActionMasked();
@@ -678,7 +563,7 @@
}
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 @@
}
clearLastDownEvent();
mLastDownEvent = MotionEvent.obtain(event);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
transitionToState(STATE_MAGNIFIED_INTERACTION);
@@ -698,7 +584,8 @@
} 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 @@
transitionToDelegatingStateAndClear();
}
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (mLastDownEvent == null) {
return;
@@ -715,8 +603,8 @@
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 @@
}
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 @@
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 @@
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,50 +721,36 @@
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onActionTapAndHold()");
}
+
clear();
mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
- mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
- down.getX(), down.getY(), true);
+
+ final float targetScale = mMagnificationController.getPersistedScale();
+ final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(scale, 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();
- }
-
- 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);
- }
-
private static final class MotionEventInfo {
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 @@
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 a5ddc12..504a7ef 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -25,6 +25,7 @@
import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener;
import android.app.IAlarmManager;
+import android.app.IUidObserver;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -41,6 +42,7 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -115,6 +117,7 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
@@ -458,7 +461,7 @@
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
int newFlags = 0;
- for (int i = 0; i < alarms.size(); ) {
+ for (int i = alarms.size()-1; i >= 0; i--) {
Alarm alarm = alarms.get(i);
if (alarm.matches(packageName)) {
alarms.remove(i);
@@ -474,7 +477,42 @@
newEnd = alarm.maxWhenElapsed;
}
newFlags |= alarm.flags;
- i++;
+ }
+ }
+ if (didRemove) {
+ // commit the new batch bounds
+ start = newStart;
+ end = newEnd;
+ flags = newFlags;
+ }
+ return didRemove;
+ }
+
+ boolean removeForStopped(final int uid) {
+ boolean didRemove = false;
+ long newStart = 0; // recalculate endpoints as we go
+ long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
+ for (int i = alarms.size()-1; i >= 0; i--) {
+ Alarm alarm = alarms.get(i);
+ try {
+ if (alarm.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+ uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+ alarms.remove(i);
+ didRemove = true;
+ if (alarm.alarmClock != null) {
+ mNextAlarmClockMayChange = true;
+ }
+ } else {
+ if (alarm.whenElapsed > newStart) {
+ newStart = alarm.whenElapsed;
+ }
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
+ }
+ newFlags |= alarm.flags;
+ }
+ } catch (RemoteException e) {
}
}
if (didRemove) {
@@ -889,6 +927,13 @@
Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
}
+ try {
+ ActivityManagerNative.getDefault().registerUidObserver(new UidObserver(),
+ ActivityManager.UID_OBSERVER_IDLE);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
publishBinderService(Context.ALARM_SERVICE, mService);
}
@@ -897,6 +942,8 @@
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mConstants.start(getContext().getContentResolver());
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mLocalDeviceIdleController
+ = LocalServices.getService(DeviceIdleController.LocalService.class);
}
}
@@ -1032,6 +1079,15 @@
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(callingUid, callingPackage)
+ == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ + " -- package not allowed to start");
+ return;
+ }
+ } catch (RemoteException e) {
+ }
removeLocked(operation, directReceiver);
setImplLocked(a, false, doValidate);
}
@@ -1838,6 +1894,37 @@
}
}
+ void removeForStoppedLocked(int uid) {
+ boolean didRemove = false;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ didRemove |= b.removeForStopped(uid);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
+ }
+ }
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ final Alarm a = mPendingWhileIdleAlarms.get(i);
+ try {
+ if (a.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+ uid, a.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ if (didRemove) {
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "remove(package) changed bounds; rebatching");
+ }
+ rebatchAllAlarmsLocked(true);
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+ }
+
void removeUserLocked(int userHandle) {
boolean didRemove = false;
for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2468,10 +2555,9 @@
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 +2597,12 @@
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;
@@ -2665,7 +2757,22 @@
}
}
}
-
+
+ final class UidObserver extends IUidObserver.Stub {
+ @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+ }
+
+ @Override public void onUidGone(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidActive(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidIdle(int uid) throws RemoteException {
+ removeForStoppedLocked(uid);
+ }
+ };
+
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
int uid = pi.getCreatorUid();
@@ -2740,6 +2847,7 @@
}
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 +2981,7 @@
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/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 96c1e2a..c131628 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -54,7 +54,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -557,12 +556,12 @@
ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
try {
if (reportedPackageNames == null) {
- callback.mCallback.opChanged(code, null);
+ callback.mCallback.opChanged(code, uid, null);
} else {
final int reportedPackageCount = reportedPackageNames.size();
for (int j = 0; j < reportedPackageCount; j++) {
String reportedPackageName = reportedPackageNames.valueAt(j);
- callback.mCallback.opChanged(code, reportedPackageName);
+ callback.mCallback.opChanged(code, uid, reportedPackageName);
}
}
} catch (RemoteException e) {
@@ -620,7 +619,7 @@
try {
for (int i = 0; i < repCbs.size(); i++) {
try {
- repCbs.get(i).mCallback.opChanged(code, packageName);
+ repCbs.get(i).mCallback.opChanged(code, uid, packageName);
} catch (RemoteException e) {
}
}
@@ -630,39 +629,51 @@
}
}
- private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
- HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
- String packageName, int op, ArrayList<Callback> cbs) {
+ private static HashMap<Callback, ArrayList<ChangeRec>> addCallbacks(
+ HashMap<Callback, ArrayList<ChangeRec>> callbacks,
+ int op, int uid, String packageName, ArrayList<Callback> cbs) {
if (cbs == null) {
return callbacks;
}
if (callbacks == null) {
- callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+ callbacks = new HashMap<>();
}
boolean duplicate = false;
for (int i=0; i<cbs.size(); i++) {
Callback cb = cbs.get(i);
- ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
+ ArrayList<ChangeRec> reports = callbacks.get(cb);
if (reports == null) {
- reports = new ArrayList<Pair<String, Integer>>();
+ reports = new ArrayList<>();
callbacks.put(cb, reports);
} else {
final int reportCount = reports.size();
for (int j = 0; j < reportCount; j++) {
- Pair<String, Integer> report = reports.get(j);
- if (report.second == op && report.first.equals(packageName)) {
+ ChangeRec report = reports.get(j);
+ if (report.op == op && report.pkg.equals(packageName)) {
duplicate = true;
break;
}
}
}
if (!duplicate) {
- reports.add(new Pair<>(packageName, op));
+ reports.add(new ChangeRec(op, uid, packageName));
}
}
return callbacks;
}
+ static final class ChangeRec {
+ final int op;
+ final int uid;
+ final String pkg;
+
+ ChangeRec(int _op, int _uid, String _pkg) {
+ op = _op;
+ uid = _uid;
+ pkg = _pkg;
+ }
+ }
+
@Override
public void resetAllModes(int reqUserId, String reqPackageName) {
final int callingPid = Binder.getCallingPid();
@@ -682,7 +693,7 @@
}
}
- HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
+ HashMap<Callback, ArrayList<ChangeRec>> callbacks = null;
synchronized (this) {
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -699,9 +710,9 @@
uidState.opModes = null;
}
for (String packageName : getPackagesForUid(uidState.uid)) {
- callbacks = addCallbacks(callbacks, packageName, code,
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
mOpModeWatchers.get(code));
- callbacks = addCallbacks(callbacks, packageName, code,
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
mPackageModeWatchers.get(packageName));
}
}
@@ -734,9 +745,9 @@
&& curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
changed = true;
- callbacks = addCallbacks(callbacks, packageName, curOp.op,
+ callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
mOpModeWatchers.get(curOp.op));
- callbacks = addCallbacks(callbacks, packageName, curOp.op,
+ callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
mPackageModeWatchers.get(packageName));
if (curOp.time == 0 && curOp.rejectTime == 0) {
pkgOps.removeAt(j);
@@ -757,13 +768,13 @@
}
}
if (callbacks != null) {
- for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+ for (Map.Entry<Callback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
Callback cb = ent.getKey();
- ArrayList<Pair<String, Integer>> reports = ent.getValue();
+ ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
- Pair<String, Integer> rep = reports.get(i);
+ ChangeRec rep = reports.get(i);
try {
- cb.mCallback.opChanged(rep.second, rep.first);
+ cb.mCallback.opChanged(rep.op, rep.uid, rep.pkg);
} catch (RemoteException e) {
}
}
@@ -1163,8 +1174,10 @@
if (pkgUid != uid) {
// Oops! The package name is not valid for the uid they are calling
// under. Abort.
+ RuntimeException ex = new RuntimeException("here");
+ ex.fillInStackTrace();
Slog.w(TAG, "Bad call: specified package " + packageName
- + " under uid " + uid + " but it is really " + pkgUid);
+ + " under uid " + uid + " but it is really " + pkgUid, ex);
return null;
}
} finally {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 927b995..485e26b 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.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 @@
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 @@
}
} 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 @@
// 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 @@
synchronized (this) {
mNotMoving = true;
if (mLocated) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:stationary");
}
}
}
@@ -804,11 +817,18 @@
} 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 @@
}
@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 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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
}
mLocated = true;
if (mNotMoving) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:location");
}
}
@@ -1628,7 +1752,7 @@
}
mLocated = true;
if (mNotMoving) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:gps");
}
}
@@ -1933,7 +2057,7 @@
long token = Binder.clearCallingIdentity();
try {
exitForceIdleLocked();
- stepIdleStateLocked();
+ stepIdleStateLocked("s:shell");
pw.print("Stepped to: ");
pw.println(stateToString(mState));
} finally {
@@ -1947,7 +2071,7 @@
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 @@
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 @@
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 @@
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 3359060..43d10c7 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -226,7 +226,7 @@
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 37dd884..f89155d 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.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 @@
}
@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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 30565c6..4d05f9a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -168,13 +168,10 @@
*/
class ServiceMap extends Handler {
final int mUserId;
- final ArrayMap<ComponentName, ServiceRecord> mServicesByName
- = new ArrayMap<ComponentName, ServiceRecord>();
- final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
- = new ArrayMap<Intent.FilterComparison, ServiceRecord>();
+ final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+ final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
- final ArrayList<ServiceRecord> mDelayedStartList
- = new ArrayList<ServiceRecord>();
+ final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
/* XXX eventually I'd like to have this based on processes instead of services.
* That is, if we try to start two services in a row both running in the same
* process, this should be one entry in mStartingBackground for that one process
@@ -185,8 +182,7 @@
= new ArrayList<DelayingProcess>();
*/
- final ArrayList<ServiceRecord> mStartingBackground
- = new ArrayList<ServiceRecord>();
+ final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
static final int MSG_BG_START_TIMEOUT = 1;
@@ -338,7 +334,7 @@
ServiceRecord r = res.record;
if (!mAm.mUserController.exists(r.userId)) {
- Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
+ Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
return null;
}
@@ -510,6 +506,35 @@
return 0;
}
+ void stopInBackgroundLocked(int uid) {
+ // Stop all services associated with this uid due to it going to the background
+ // stopped state.
+ ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
+ ArrayList<ServiceRecord> stopping = null;
+ if (services != null) {
+ for (int i=services.mServicesByName.size()-1; i>=0; i--) {
+ ServiceRecord service = services.mServicesByName.valueAt(i);
+ if (service.appInfo.uid == uid && service.startRequested) {
+ if (mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+ uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
+ if (stopping == null) {
+ stopping = new ArrayList<>();
+ stopping.add(service);
+ }
+ }
+ }
+ }
+ if (stopping != null) {
+ for (int i=stopping.size()-1; i>=0; i--) {
+ ServiceRecord service = stopping.get(i);
+ service.delayed = false;
+ services.ensureNotStartingBackground(service);
+ stopServiceLocked(service);
+ }
+ }
+ }
+ }
+
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1069,6 +1094,17 @@
}
r = smap.mServicesByName.get(name);
if (r == null && createIfNeeded) {
+ // Before going further -- if this app is not allowed to run in the background,
+ // then at this point we aren't going to let it period.
+ if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+ sInfo.packageName, callingPid)) {
+ Slog.w(TAG, "Background execution not allowed: service "
+ + r.intent + " to " + name.flattenToShortString()
+ + " from pid=" + callingPid + " uid=" + callingUid
+ + " pkg=" + callingPackage);
+ return null;
+ }
+
Intent.FilterComparison filter
= new Intent.FilterComparison(service.cloneFilter());
ServiceRestarter res = new ServiceRestarter();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 566065c..025126f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,7 +60,6 @@
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
-import android.content.pm.AppsQueryHelper;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -88,6 +87,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.DumpHeapActivity;
+import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
@@ -109,15 +109,12 @@
import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.ActivityStackSupervisor.ActivityDisplay;
import com.android.server.firewall.IntentFirewall;
import com.android.server.pm.Installer;
-import com.android.server.pm.UserManagerService;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.AppTransition;
import com.android.server.wm.WindowManagerService;
@@ -388,6 +385,10 @@
// Maximum number of users we allow to be running at a time.
static final int MAX_RUNNING_USERS = 3;
+ // This is the amount of time we allow an app to settle after it goes into the background,
+ // before we start restricting what it can do.
+ static final int BACKGROUND_SETTLE_TIME = 1*60*1000;
+
// How long to wait in getAssistContextExtras for the activity and foreground services
// to respond with the result.
static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -716,6 +717,12 @@
final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
/**
+ * This is for verifying the UID report flow.
+ */
+ static final boolean VALIDATE_UID_STATES = true;
+ final SparseArray<UidRecord> mValidateUids = new SparseArray<>();
+
+ /**
* Packages that the user has asked to have run in screen size
* compatibility mode instead of filling the screen.
*/
@@ -1310,29 +1317,29 @@
}
}
- static final int SHOW_ERROR_MSG = 1;
- static final int SHOW_NOT_RESPONDING_MSG = 2;
- static final int SHOW_FACTORY_ERROR_MSG = 3;
+ static final int SHOW_ERROR_UI_MSG = 1;
+ static final int SHOW_NOT_RESPONDING_UI_MSG = 2;
+ static final int SHOW_FACTORY_ERROR_UI_MSG = 3;
static final int UPDATE_CONFIGURATION_MSG = 4;
static final int GC_BACKGROUND_PROCESSES_MSG = 5;
- static final int WAIT_FOR_DEBUGGER_MSG = 6;
+ static final int WAIT_FOR_DEBUGGER_UI_MSG = 6;
static final int SERVICE_TIMEOUT_MSG = 12;
static final int UPDATE_TIME_ZONE = 13;
- static final int SHOW_UID_ERROR_MSG = 14;
- static final int SHOW_FINGERPRINT_ERROR_MSG = 15;
+ static final int SHOW_UID_ERROR_UI_MSG = 14;
+ static final int SHOW_FINGERPRINT_ERROR_UI_MSG = 15;
static final int PROC_START_TIMEOUT_MSG = 20;
static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
static final int KILL_APPLICATION_MSG = 22;
static final int FINALIZE_PENDING_INTENT_MSG = 23;
static final int POST_HEAVY_NOTIFICATION_MSG = 24;
static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
- static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+ static final int SHOW_STRICT_MODE_VIOLATION_UI_MSG = 26;
static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
static final int CLEAR_DNS_CACHE_MSG = 28;
static final int UPDATE_HTTP_PROXY_MSG = 29;
- static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
- static final int DISPATCH_PROCESSES_CHANGED = 31;
- static final int DISPATCH_PROCESS_DIED = 32;
+ static final int SHOW_COMPAT_MODE_DIALOG_UI_MSG = 30;
+ static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
+ static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
static final int REPORT_MEM_USAGE_MSG = 33;
static final int REPORT_USER_SWITCH_MSG = 34;
static final int CONTINUE_USER_SWITCH_MSG = 35;
@@ -1346,20 +1353,21 @@
static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
static final int FINISH_BOOTING_MSG = 45;
- static final int START_USER_SWITCH_MSG = 46;
+ static final int START_USER_SWITCH_UI_MSG = 46;
static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
- static final int DISMISS_DIALOG_MSG = 48;
+ static final int DISMISS_DIALOG_UI_MSG = 48;
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
static final int DELETE_DUMPHEAP_MSG = 52;
static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
- static final int DISPATCH_UIDS_CHANGED_MSG = 54;
+ static final int DISPATCH_UIDS_CHANGED_UI_MSG = 54;
static final int REPORT_TIME_TRACKER_MSG = 55;
static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
static final int APP_BOOST_DEACTIVATE_MSG = 58;
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
+ static final int IDLE_UIDS_MSG = 60;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1394,7 +1402,7 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case SHOW_ERROR_MSG: {
+ case SHOW_ERROR_UI_MSG: {
HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
@@ -1439,7 +1447,7 @@
ensureBootCompleted();
} break;
- case SHOW_NOT_RESPONDING_MSG: {
+ case SHOW_NOT_RESPONDING_UI_MSG: {
synchronized (ActivityManagerService.this) {
HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
ProcessRecord proc = (ProcessRecord)data.get("app");
@@ -1471,7 +1479,7 @@
ensureBootCompleted();
} break;
- case SHOW_STRICT_MODE_VIOLATION_MSG: {
+ case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
synchronized (ActivityManagerService.this) {
ProcessRecord proc = (ProcessRecord) data.get("app");
@@ -1497,13 +1505,13 @@
}
ensureBootCompleted();
} break;
- case SHOW_FACTORY_ERROR_MSG: {
+ case SHOW_FACTORY_ERROR_UI_MSG: {
Dialog d = new FactoryErrorDialog(
mContext, msg.getData().getCharSequence("msg"));
d.show();
ensureBootCompleted();
} break;
- case WAIT_FOR_DEBUGGER_MSG: {
+ case WAIT_FOR_DEBUGGER_UI_MSG: {
synchronized (ActivityManagerService.this) {
ProcessRecord app = (ProcessRecord)msg.obj;
if (msg.arg1 != 0) {
@@ -1523,7 +1531,7 @@
}
}
} break;
- case SHOW_UID_ERROR_MSG: {
+ case SHOW_UID_ERROR_UI_MSG: {
if (mShowDialogs) {
AlertDialog d = new BaseErrorDialog(mContext);
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1531,11 +1539,11 @@
d.setTitle(mContext.getText(R.string.android_system_label));
d.setMessage(mContext.getText(R.string.system_error_wipe_data));
d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
- obtainMessage(DISMISS_DIALOG_MSG, d));
+ obtainMessage(DISMISS_DIALOG_UI_MSG, d));
d.show();
}
} break;
- case SHOW_FINGERPRINT_ERROR_MSG: {
+ case SHOW_FINGERPRINT_ERROR_UI_MSG: {
if (mShowDialogs) {
AlertDialog d = new BaseErrorDialog(mContext);
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1543,11 +1551,11 @@
d.setTitle(mContext.getText(R.string.android_system_label));
d.setMessage(mContext.getText(R.string.system_error_manufacturer));
d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
- obtainMessage(DISMISS_DIALOG_MSG, d));
+ obtainMessage(DISMISS_DIALOG_UI_MSG, d));
d.show();
}
} break;
- case SHOW_COMPAT_MODE_DIALOG_MSG: {
+ case SHOW_COMPAT_MODE_DIALOG_UI_MSG: {
synchronized (ActivityManagerService.this) {
ActivityRecord ar = (ActivityRecord) msg.obj;
if (mCompatModeDialog != null) {
@@ -1575,26 +1583,26 @@
}
break;
}
- case START_USER_SWITCH_MSG: {
+ case START_USER_SWITCH_UI_MSG: {
mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
break;
}
- case DISMISS_DIALOG_MSG: {
+ case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
break;
}
- case DISPATCH_PROCESSES_CHANGED: {
+ case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
dispatchProcessesChanged();
break;
}
- case DISPATCH_PROCESS_DIED: {
+ case DISPATCH_PROCESS_DIED_UI_MSG: {
final int pid = msg.arg1;
final int uid = msg.arg2;
dispatchProcessDied(pid, uid);
break;
}
- case DISPATCH_UIDS_CHANGED_MSG: {
+ case DISPATCH_UIDS_CHANGED_UI_MSG: {
dispatchUidsChanged();
} break;
}
@@ -2046,7 +2054,7 @@
// it is finished we make sure it is reset to its default.
mUserIsMonkey = false;
} break;
- case APP_BOOST_DEACTIVATE_MSG : {
+ case APP_BOOST_DEACTIVATE_MSG: {
synchronized(ActivityManagerService.this) {
if (mIsBoosted) {
if (mBoostStartTime < (SystemClock.uptimeMillis() - APP_BOOST_TIMEOUT)) {
@@ -2060,6 +2068,9 @@
}
}
} break;
+ case IDLE_UIDS_MSG: {
+ idleUids();
+ } break;
}
}
};
@@ -2352,6 +2363,17 @@
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+ mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+ new IAppOpsCallback.Stub() {
+ @Override public void opChanged(int op, int uid, String packageName) {
+ if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
+ if (mAppOpsService.checkOperation(op, uid, packageName)
+ != AppOpsManager.MODE_ALLOWED) {
+ runInBackgroundDisabled(uid);
+ }
+ }
+ }
+ });
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
@@ -2766,7 +2788,7 @@
final void showAskCompatModeDialogLocked(ActivityRecord r) {
Message msg = Message.obtain();
- msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
+ msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
msg.obj = r.task.askedCompatMode ? null : r;
mUiHandler.sendMessage(msg);
}
@@ -3337,15 +3359,6 @@
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;
@@ -3797,8 +3810,10 @@
for (int i=0; i<N; i++) {
final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
mActiveUidChanges[i] = change;
- change.uidRecord.pendingChange = null;
- change.uidRecord = null;
+ if (change.uidRecord != null) {
+ change.uidRecord.pendingChange = null;
+ change.uidRecord = null;
+ }
}
mPendingUidChanges.clear();
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
@@ -3808,7 +3823,8 @@
if (mLocalPowerManager != null) {
for (int j=0; j<N; j++) {
UidRecord.ChangeItem item = mActiveUidChanges[j];
- if (item.gone) {
+ if (item.change == UidRecord.CHANGE_GONE
+ || item.change == UidRecord.CHANGE_GONE_IDLE) {
mLocalPowerManager.uidGone(item.uid);
} else {
mLocalPowerManager.updateUidProcState(item.uid, item.processState);
@@ -3820,19 +3836,66 @@
while (i > 0) {
i--;
final IUidObserver observer = mUidObservers.getBroadcastItem(i);
+ final int which = (Integer)mUidObservers.getBroadcastCookie(i);
if (observer != null) {
try {
for (int j=0; j<N; j++) {
UidRecord.ChangeItem item = mActiveUidChanges[j];
- if (item.gone) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID gone uid=" + item.uid);
- observer.onUidGone(item.uid);
+ final int change = item.change;
+ UidRecord validateUid = null;
+ if (VALIDATE_UID_STATES && i == 0) {
+ validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null && change != UidRecord.CHANGE_GONE
+ && change != UidRecord.CHANGE_GONE_IDLE) {
+ validateUid = new UidRecord(item.uid);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ }
+ if (change == UidRecord.CHANGE_IDLE
+ || change == UidRecord.CHANGE_GONE_IDLE) {
+ if ((which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID idle uid=" + item.uid);
+ observer.onUidIdle(item.uid);
+ }
+ if (VALIDATE_UID_STATES && i == 0) {
+ if (validateUid != null) {
+ validateUid.idle = true;
+ }
+ }
+ } else if (change == UidRecord.CHANGE_ACTIVE) {
+ if ((which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID active uid=" + item.uid);
+ observer.onUidActive(item.uid);
+ }
+ if (VALIDATE_UID_STATES && i == 0) {
+ validateUid.idle = false;
+ }
+ }
+ if (change == UidRecord.CHANGE_GONE
+ || change == UidRecord.CHANGE_GONE_IDLE) {
+ if ((which & ActivityManager.UID_OBSERVER_GONE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID gone uid=" + item.uid);
+ observer.onUidGone(item.uid);
+ }
+ if (VALIDATE_UID_STATES && i == 0) {
+ if (validateUid != null) {
+ mValidateUids.remove(item.uid);
+ }
+ }
} else {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID CHANGED uid=" + item.uid
- + ": " + item.processState);
- observer.onUidStateChanged(item.uid, item.processState);
+ if ((which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID CHANGED uid=" + item.uid
+ + ": " + item.processState);
+ observer.onUidStateChanged(item.uid, item.processState);
+ }
+ if (VALIDATE_UID_STATES && i == 0) {
+ validateUid.curProcState = validateUid.setProcState
+ = item.processState;
+ }
}
}
} catch (RemoteException e) {
@@ -5102,7 +5165,7 @@
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
HashMap<String, Object> map = new HashMap<String, Object>();
- msg.what = SHOW_NOT_RESPONDING_MSG;
+ msg.what = SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = map;
msg.arg1 = aboveSystem ? 1 : 0;
map.put("app", app);
@@ -5881,7 +5944,7 @@
// No more processes using this uid, tell clients it is gone.
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"No more processes in " + old.uidRecord);
- enqueueUidChangeLocked(old.uidRecord, true);
+ enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
mActiveUids.remove(uid);
}
old.uidRecord = null;
@@ -5907,7 +5970,7 @@
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"Creating new process uid: " + uidRec);
mActiveUids.put(proc.uid, uidRec);
- enqueueUidChangeLocked(uidRec, false);
+ enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
}
proc.uidRecord = uidRec;
uidRec.numProcs++;
@@ -6715,9 +6778,11 @@
if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
activity = ActivityRecord.isInStackLocked(token);
if (activity == null) {
+ Slog.w(TAG, "Failed createPendingResult: activity " + token + " not in any stack");
return null;
}
if (activity.finishing) {
+ Slog.w(TAG, "Failed createPendingResult: activity " + activity + " is finishing");
return null;
}
}
@@ -7269,6 +7334,36 @@
return readMet && writeMet;
}
+ public int getAppStartMode(int uid, String packageName) {
+ synchronized (this) {
+ boolean bg = checkAllowBackgroundLocked(uid, packageName, -1);
+ return bg ? ActivityManager.APP_START_MODE_NORMAL
+ : ActivityManager.APP_START_MODE_DISABLED;
+ }
+ }
+
+ boolean checkAllowBackgroundLocked(int uid, String packageName, int callingPid) {
+ UidRecord uidRec = mActiveUids.get(uid);
+ if (uidRec == null || uidRec.idle) {
+ if (callingPid >= 0) {
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ proc = mPidsSelfLocked.get(callingPid);
+ }
+ if (proc != null && proc.curProcState < ActivityManager.PROCESS_STATE_RECEIVER) {
+ // Whoever is instigating this is in the foreground, so we will allow it
+ // to go through.
+ return true;
+ }
+ }
+ if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName)
+ != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private ProviderInfo getProviderInfoLocked(String authority, int userHandle) {
ProviderInfo pi = null;
ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -8268,7 +8363,7 @@
if (app == null) return;
Message msg = Message.obtain();
- msg.what = WAIT_FOR_DEBUGGER_MSG;
+ msg.what = WAIT_FOR_DEBUGGER_UI_MSG;
msg.obj = app;
msg.arg1 = waiting ? 1 : 0;
mUiHandler.sendMessage(msg);
@@ -11213,11 +11308,12 @@
}
}
- public void registerUidObserver(IUidObserver observer) {
+ @Override
+ public void registerUidObserver(IUidObserver observer, int which) {
enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
"registerUidObserver()");
synchronized (this) {
- mUidObservers.register(observer);
+ mUidObservers.register(observer, which);
}
}
@@ -12056,7 +12152,7 @@
mTopData = null;
mTopComponent = null;
Message msg = Message.obtain();
- msg.what = SHOW_FACTORY_ERROR_MSG;
+ msg.what = SHOW_FACTORY_ERROR_UI_MSG;
msg.getData().putCharSequence("msg", errorMsg);
mUiHandler.sendMessage(msg);
}
@@ -12110,14 +12206,14 @@
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your"
+ " data partition or your device will be unstable.");
- mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
+ mUiHandler.obtainMessage(SHOW_UID_ERROR_UI_MSG).sendToTarget();
}
} catch (RemoteException e) {
}
if (!Build.isBuildConsistent()) {
Slog.e(TAG, "Build fingerprint is not consistent, warning user");
- mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
+ mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_UI_MSG).sendToTarget();
}
long ident = Binder.clearCallingIdentity();
@@ -12398,7 +12494,7 @@
final long origId = Binder.clearCallingIdentity();
Message msg = Message.obtain();
- msg.what = SHOW_STRICT_MODE_VIOLATION_MSG;
+ msg.what = SHOW_STRICT_MODE_VIOLATION_UI_MSG;
HashMap<String, Object> data = new HashMap<String, Object>();
data.put("result", result);
data.put("app", r);
@@ -12855,7 +12951,7 @@
}
Message msg = Message.obtain();
- msg.what = SHOW_ERROR_MSG;
+ msg.what = SHOW_ERROR_UI_MSG;
HashMap data = new HashMap();
data.put("result", result);
data.put("app", r);
@@ -13484,6 +13580,39 @@
}
}
+ boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+ String header, boolean needSep) {
+ boolean printed = false;
+ int whichAppId = -1;
+ if (dumpPackage != null) {
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+ dumpPackage, 0);
+ whichAppId = UserHandle.getAppId(info.uid);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ for (int i=0; i<uids.size(); i++) {
+ UidRecord uidRec = uids.valueAt(i);
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ continue;
+ }
+ if (!printed) {
+ printed = true;
+ if (needSep) {
+ pw.println();
+ }
+ pw.print(" ");
+ pw.println(header);
+ needSep = true;
+ }
+ pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
+ pw.print(": "); pw.println(uidRec);
+ }
+ return printed;
+ }
+
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
@@ -13540,33 +13669,13 @@
}
if (mActiveUids.size() > 0) {
- boolean printed = false;
- int whichAppId = -1;
- if (dumpPackage != null) {
- try {
- ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
- dumpPackage, 0);
- whichAppId = UserHandle.getAppId(info.uid);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
+ if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
+ printedAnything = needSep = true;
}
- for (int i=0; i<mActiveUids.size(); i++) {
- UidRecord uidRec = mActiveUids.valueAt(i);
- if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
- continue;
- }
- if (!printed) {
- printed = true;
- if (needSep) {
- pw.println();
- }
- pw.println(" UID states:");
- needSep = true;
- printedAnything = true;
- }
- pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
- pw.print(": "); pw.println(uidRec);
+ }
+ if (mValidateUids.size() > 0) {
+ if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
+ printedAnything = needSep = true;
}
}
@@ -14457,6 +14566,9 @@
if (object1.first.setAdj != object2.first.setAdj) {
return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
}
+ if (object1.first.setProcState != object2.first.setProcState) {
+ return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
+ }
if (object1.second.intValue() != object2.second.intValue()) {
return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
}
@@ -15829,7 +15941,8 @@
mAvailProcessChanges.add(item);
}
}
- mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+ mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, app.pid, app.info.uid,
+ null).sendToTarget();
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
@@ -18894,8 +19007,6 @@
}
}
}
- Process.setSwappiness(app.pid,
- app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
}
}
if (app.repForegroundActivities != app.foregroundActivities) {
@@ -19020,7 +19131,7 @@
if (mPendingProcessChanges.size() == 0) {
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"*** Enqueueing dispatch processes changed!");
- mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+ mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
}
mPendingProcessChanges.add(item);
}
@@ -19039,29 +19150,46 @@
return success;
}
- private final void enqueueUidChangeLocked(UidRecord uidRec, boolean gone) {
- if (uidRec.pendingChange == null) {
+ private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+ final UidRecord.ChangeItem pendingChange;
+ if (uidRec == null || uidRec.pendingChange == null) {
if (mPendingUidChanges.size() == 0) {
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"*** Enqueueing dispatch uid changed!");
- mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_MSG).sendToTarget();
+ mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
}
final int NA = mAvailUidChanges.size();
if (NA > 0) {
- uidRec.pendingChange = mAvailUidChanges.remove(NA-1);
+ pendingChange = mAvailUidChanges.remove(NA-1);
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Retrieving available item: " + uidRec.pendingChange);
+ "Retrieving available item: " + pendingChange);
} else {
- uidRec.pendingChange = new UidRecord.ChangeItem();
+ pendingChange = new UidRecord.ChangeItem();
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Allocating new item: " + uidRec.pendingChange);
+ "Allocating new item: " + pendingChange);
}
- uidRec.pendingChange.uidRecord = uidRec;
- uidRec.pendingChange.uid = uidRec.uid;
- mPendingUidChanges.add(uidRec.pendingChange);
+ if (uidRec != null) {
+ uidRec.pendingChange = pendingChange;
+ if (change == UidRecord.CHANGE_GONE && !uidRec.idle) {
+ // If this uid is going away, and we haven't yet reported it is gone,
+ // then do so now.
+ change = UidRecord.CHANGE_GONE_IDLE;
+ }
+ } else if (uid < 0) {
+ throw new IllegalArgumentException("No UidRecord or uid");
+ }
+ pendingChange.uidRecord = uidRec;
+ pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+ mPendingUidChanges.add(pendingChange);
+ } else {
+ pendingChange = uidRec.pendingChange;
+ if (change == UidRecord.CHANGE_GONE && pendingChange.change == UidRecord.CHANGE_IDLE) {
+ change = UidRecord.CHANGE_GONE_IDLE;
+ }
}
- uidRec.pendingChange.gone = gone;
- uidRec.pendingChange.processState = uidRec.setProcState;
+ pendingChange.change = change;
+ pendingChange.processState = uidRec != null
+ ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
}
private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
@@ -19608,12 +19736,31 @@
// Update from any uid changes.
for (int i=mActiveUids.size()-1; i>=0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
+ int uidChange = UidRecord.CHANGE_PROCSTATE;
if (uidRec.setProcState != uidRec.curProcState) {
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"Changes in " + uidRec + ": proc state from " + uidRec.setProcState
+ " to " + uidRec.curProcState);
+ if (ActivityManager.isProcStateBackground(uidRec.curProcState)) {
+ if (!ActivityManager.isProcStateBackground(uidRec.setProcState)) {
+ uidRec.lastBackgroundTime = nowElapsed;
+ if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ // Note: the background settle time is in elapsed realtime, while
+ // the handler time base is uptime. All this means is that we may
+ // stop background uids later than we had intended, but that only
+ // happens because the device was sleeping so we are okay anyway.
+ mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, BACKGROUND_SETTLE_TIME);
+ }
+ }
+ } else {
+ if (uidRec.idle) {
+ uidChange = UidRecord.CHANGE_ACTIVE;
+ uidRec.idle = false;
+ }
+ uidRec.lastBackgroundTime = 0;
+ }
uidRec.setProcState = uidRec.curProcState;
- enqueueUidChangeLocked(uidRec, false);
+ enqueueUidChangeLocked(uidRec, -1, uidChange);
}
}
@@ -19638,6 +19785,53 @@
}
}
+ final void idleUids() {
+ synchronized (this) {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long maxBgTime = nowElapsed - BACKGROUND_SETTLE_TIME;
+ long nextTime = 0;
+ for (int i=mActiveUids.size()-1; i>=0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ final long bgTime = uidRec.lastBackgroundTime;
+ if (bgTime > 0 && !uidRec.idle) {
+ if (bgTime <= maxBgTime) {
+ uidRec.idle = true;
+ doStopUidLocked(uidRec.uid, uidRec);
+ } else {
+ if (nextTime == 0 || nextTime > bgTime) {
+ nextTime = bgTime;
+ }
+ }
+ }
+ }
+ if (nextTime > 0) {
+ mHandler.removeMessages(IDLE_UIDS_MSG);
+ mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+ nextTime + BACKGROUND_SETTLE_TIME - nowElapsed);
+ }
+ }
+ }
+
+ final void runInBackgroundDisabled(int uid) {
+ synchronized (this) {
+ UidRecord uidRec = mActiveUids.get(uid);
+ if (uidRec != null) {
+ // This uid is actually running... should it be considered background now?
+ if (uidRec.idle) {
+ doStopUidLocked(uidRec.uid, uidRec);
+ }
+ } else {
+ // This uid isn't actually running... still send a report about it being "stopped".
+ doStopUidLocked(uid, null);
+ }
+ }
+ }
+
+ final void doStopUidLocked(int uid, final UidRecord uidRec) {
+ mServices.stopInBackgroundLocked(uid);
+ enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+ }
+
final void trimApplications() {
synchronized (this) {
int i;
@@ -19973,8 +20167,9 @@
userName = userInfo.name;
mUserController.setTargetUserIdLocked(userId);
}
- mUiHandler.removeMessages(START_USER_SWITCH_MSG);
- mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
+ mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+ mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
+ userName));
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d1e7e85..13c1417 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 @@
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 @@
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 @@
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/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 13d3ee1..0ec4b18 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 @@
// 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/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d317791..fb37eda 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -556,6 +556,17 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
+ if (!skip) {
+ if (!mService.checkAllowBackgroundLocked(filter.receiverList.uid, filter.packageName,
+ -1)) {
+ Slog.w(TAG, "Background execution not allowed: receiving "
+ + r.intent
+ + " to " + filter.receiverList.app
+ + " (pid=" + filter.receiverList.pid
+ + ", uid=" + filter.receiverList.uid + ")");
+ skip = true;
+ }
+ }
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -938,6 +949,15 @@
skip = true;
}
if (!skip) {
+ if (!mService.checkAllowBackgroundLocked(info.activityInfo.applicationInfo.uid,
+ info.activityInfo.packageName, -1)) {
+ Slog.w(TAG, "Background execution not allowed: receiving "
+ + r.intent + " to "
+ + component.flattenToShortString());
+ skip = true;
+ }
+ }
+ if (!skip) {
skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index b4efbf0..d24c3a5 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,7 +17,9 @@
package com.android.server.am;
import android.app.ActivityManager;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.TimeUtils;
/**
* Overall information about a uid that has actively running processes.
@@ -26,12 +28,20 @@
final int uid;
int curProcState;
int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ long lastBackgroundTime;
+ boolean idle;
int numProcs;
+ static final int CHANGE_PROCSTATE = 0;
+ static final int CHANGE_GONE = 1;
+ static final int CHANGE_GONE_IDLE = 2;
+ static final int CHANGE_IDLE = 3;
+ static final int CHANGE_ACTIVE = 4;
+
static final class ChangeItem {
UidRecord uidRecord;
int uid;
- boolean gone;
+ int change;
int processState;
}
@@ -54,9 +64,16 @@
UserHandle.formatUid(sb, uid);
sb.append(' ');
sb.append(ProcessList.makeProcStateString(curProcState));
- sb.append(" / ");
+ if (lastBackgroundTime > 0) {
+ sb.append(" bg:");
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+ }
+ if (idle) {
+ sb.append(" idle");
+ }
+ sb.append(" procs:");
sb.append(numProcs);
- sb.append(" procs}");
+ sb.append("}");
return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index b766894..75a74c0 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -466,7 +466,7 @@
if (cname == null) {
info = new SyncStorageEngine.EndPoint(account, authority, userId);
} else {
- info = new SyncStorageEngine.EndPoint(cname, userId);
+ info = new SyncStorageEngine.EndPoint(cname, userId, -1);
}
syncManager.clearScheduledSyncOperations(info);
syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 82e0eaf..4f53882 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,8 +21,10 @@
import android.accounts.AccountManager;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.AppGlobals;
+import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -79,11 +81,14 @@
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import com.android.internal.R;
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 +212,7 @@
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;
@@ -234,7 +240,7 @@
private final AppIdleMonitor mAppIdleMonitor;
- private BroadcastReceiver mStorageIntentReceiver =
+ private final BroadcastReceiver mStorageIntentReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -257,7 +263,7 @@
}
};
- private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
boolean idle = mPowerManager.isDeviceIdleMode()
|| mPowerManager.isLightDeviceIdleMode();
@@ -267,12 +273,18 @@
SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
null /* any sync */);
} else {
+ if (mLocalDeviceIdleController != null) {
+ if (!mReportedSyncActive) {
+ mReportedSyncActive = true;
+ mLocalDeviceIdleController.setSyncActive(true);
+ }
+ }
sendCheckAlarmsMessage();
}
}
};
- private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mBootCompleted = true;
@@ -280,7 +292,7 @@
}
};
- private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateRunningAccounts();
@@ -291,7 +303,23 @@
}
};
+ private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+ }
+
+ @Override public void onUidGone(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidActive(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidIdle(int uid) throws RemoteException {
+ cancelSyncsForUid(uid);
+ }
+ };
+
private final PowerManager mPowerManager;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
// Use this as a random offset to seed all periodic syncs.
private int mSyncRandomOffsetMillis;
@@ -494,6 +522,13 @@
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+ try {
+ ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_IDLE);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
if (!factoryTest) {
mNotificationMgr = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -651,6 +686,26 @@
Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
}
+ final android.content.pm.ServiceInfo sinfo;
+ try {
+ sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Not scheduling sync " + cname
+ + " -- can't find service for user " + userId);
+ return;
+ }
+ final int sUid = sinfo.applicationInfo.uid;
+
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(sUid, cname.getPackageName())
+ == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Not scheduling sync " + sUid + ":" + cname
+ + " -- package not allowed to start");
+ return;
+ }
+ } catch (RemoteException e) {
+ }
+
Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
if (expedited) {
runtimeMillis = -1; // this means schedule at the front of the queue
@@ -678,7 +733,7 @@
}
return;
}
- SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+ SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId, sUid);
Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
final long backoffTime = backoff != null ? backoff.first : 0;
@@ -692,7 +747,7 @@
+ ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(cname, userId, uid, source, extras,
+ new SyncOperation(cname, userId, sUid, cname.getPackageName(), uid, source, extras,
runtimeMillis /* runtime */,
beforeRunTimeMillis /* flextime */,
backoffTime,
@@ -827,6 +882,18 @@
if (syncAdapterInfo == null) {
continue;
}
+ final int owningUid = syncAdapterInfo.uid;
+ final String owningPackage = syncAdapterInfo.componentName.getPackageName();
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(owningUid,
+ owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+ + syncAdapterInfo.componentName
+ + " -- package not allowed to start");
+ return;
+ }
+ } catch (RemoteException e) {
+ }
final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
if (isSyncable < 0 && isAlwaysSyncable) {
@@ -876,7 +943,8 @@
+ ", extras " + newExtras);
}
scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, reason, source,
+ new SyncOperation(account.account, account.userId,
+ owningUid, owningPackage, reason, source,
authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
backoffTime, delayUntil, allowParallelSyncs));
}
@@ -892,7 +960,8 @@
+ ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, reason, source,
+ new SyncOperation(account.account, account.userId,
+ owningUid, owningPackage, reason, source,
authority, extras, runtimeMillis, beforeRuntimeMillis,
backoffTime, delayUntil, allowParallelSyncs));
}
@@ -1299,6 +1368,14 @@
}
}
+ void cancelSyncsForUid(int uid) {
+ synchronized (mSyncQueue) {
+ if (mSyncQueue.removeUidIfNeededLocked(uid)) {
+ sendCheckAlarmsMessage();
+ }
+ }
+ }
+
/**
* @hide
*/
@@ -1470,6 +1547,7 @@
}
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();
@@ -2155,8 +2233,8 @@
/**
* Stash any messages that come to the handler before boot is complete or before the device
* is properly provisioned (i.e. out of set-up wizard).
- * {@link #onBootCompleted()} and {@link #onDeviceProvisioned(boolean)} both need to come
- * in before we start syncing.
+ * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
+ * need to come in before we start syncing.
* @param msg Message to dispatch at a later point.
* @return true if a message was enqueued, false otherwise. This is to avoid losing the
* message if we manage to acquire the lock but by the time we do boot has completed.
@@ -2492,6 +2570,8 @@
}
scheduleSyncOperation(
new SyncOperation(target.account, target.userId,
+ syncAdapterInfo.uid,
+ syncAdapterInfo.componentName.getPackageName(),
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_PERIODIC,
target.provider, extras,
@@ -2502,6 +2582,7 @@
} else if (target.target_service) {
scheduleSyncOperation(
new SyncOperation(target.service, target.userId,
+ target.serviceUid, target.service.getPackageName(),
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_PERIODIC,
extras,
@@ -2544,6 +2625,7 @@
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2551,6 +2633,7 @@
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: memory low, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2558,6 +2641,7 @@
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: device idle, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2567,6 +2651,7 @@
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2772,9 +2857,25 @@
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);
@@ -3114,6 +3215,7 @@
if (syncResult != null && syncResult.fullSyncRequested) {
scheduleSyncOperation(
new SyncOperation(info.account, info.userId,
+ syncOperation.owningUid, syncOperation.owningPackage,
syncOperation.reason,
syncOperation.syncSource, info.provider, new Bundle(),
0 /* delay */, 0 /* flex */,
@@ -3124,6 +3226,7 @@
if (syncResult != null && syncResult.fullSyncRequested) {
scheduleSyncOperation(
new SyncOperation(info.service, info.userId,
+ syncOperation.owningUid, syncOperation.owningPackage,
syncOperation.reason,
syncOperation.syncSource, new Bundle(),
0 /* delay */, 0 /* flex */,
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 10efe81..ab777ae 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
/**
@@ -62,6 +63,8 @@
/** Identifying info for the target for this operation. */
public final SyncStorageEngine.EndPoint target;
+ public final int owningUid;
+ public final String owningPackage;
/** Why this sync was kicked off. {@link #REASON_NAMES} */
public final int reason;
/** Where this sync was initiated. */
@@ -93,25 +96,28 @@
/** Whether this sync op was recently skipped due to the app being idle */
public boolean appIdle;
- public SyncOperation(Account account, int userId, int reason, int source, String provider,
- Bundle extras, long runTimeFromNow, long flexTime, long backoff,
- long delayUntil, boolean allowParallelSyncs) {
- this(new SyncStorageEngine.EndPoint(account, provider, userId),
+ public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
+ int reason, int source, String provider, Bundle extras, long runTimeFromNow,
+ long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs) {
+ this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
allowParallelSyncs);
}
- public SyncOperation(ComponentName service, int userId, int reason, int source,
- Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+ public SyncOperation(ComponentName service, int userId, int owningUid, String owningPackage,
+ int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff,
long delayUntil) {
- this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
- runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
+ this(new SyncStorageEngine.EndPoint(service, userId, owningUid), owningUid, owningPackage,
+ reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
+ true /* allowParallelSyncs */);
}
- private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
- long runTimeFromNow, long flexTime, long backoff, long delayUntil,
- boolean allowParallelSyncs) {
+ private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
+ int reason, int source, Bundle extras, long runTimeFromNow, long flexTime,
+ long backoff, long delayUntil, boolean allowParallelSyncs) {
this.target = info;
+ this.owningUid = owningUid;
+ this.owningPackage = owningPackage;
this.reason = reason;
this.syncSource = source;
this.extras = new Bundle(extras);
@@ -142,7 +148,8 @@
/** Used to reschedule a sync at a new point in time. */
public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
- this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
+ this(other.target, other.owningUid, other.owningPackage, other.reason, other.syncSource,
+ new Bundle(other.extras),
newRunTimeFromNow,
0L /* In back-off so no flex */,
other.backoff,
@@ -228,6 +235,13 @@
}
sb.append(", reason: ");
sb.append(reasonToString(pm, reason));
+ if (!useOneLine) {
+ sb.append("\n ");
+ sb.append("owningUid=");
+ UserHandle.formatUid(sb, owningUid);
+ sb.append(" owningPackage=");
+ sb.append(owningPackage);
+ }
if (!useOneLine && !extras.keySet().isEmpty()) {
sb.append("\n ");
extrasToStringBuilder(extras, sb);
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
index 587de1c..b15d0d8 100644
--- a/services/core/java/com/android/server/content/SyncQueue.java
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -16,16 +16,20 @@
package com.android.server.content;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.content.pm.PackageManager;
import android.content.SyncAdapterType;
import android.content.SyncAdaptersCache;
import android.content.pm.RegisteredServicesCache.ServiceInfo;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import com.google.android.collect.Maps;
import java.util.ArrayList;
@@ -74,8 +78,9 @@
continue;
}
operationToAdd = new SyncOperation(
- info.account, info.userId, op.reason, op.syncSource, info.provider,
- op.extras,
+ info.account, info.userId, syncAdapterInfo.uid,
+ syncAdapterInfo.componentName.getPackageName(), op.reason,
+ op.syncSource, info.provider, op.extras,
op.expedited ? -1 : 0 /* delay */,
0 /* flex */,
backoff != null ? backoff.first : 0L,
@@ -84,16 +89,24 @@
operationToAdd.pendingOperation = op;
add(operationToAdd, op);
} else if (info.target_service) {
+ android.content.pm.ServiceInfo sinfo;
try {
- mPackageManager.getServiceInfo(info.service, 0);
+ sinfo = mPackageManager.getServiceInfo(info.service, info.userId);
} catch (PackageManager.NameNotFoundException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.w(TAG, "Missing sync service for authority " + op.target);
}
continue;
}
+ if (sinfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.w(TAG, "Missing sync service for authority " + op.target);
+ }
+ continue;
+ }
operationToAdd = new SyncOperation(
- info.service, info.userId, op.reason, op.syncSource,
+ info.service, info.userId, sinfo.applicationInfo.uid,
+ info.service.getPackageName(), op.reason, op.syncSource,
op.extras,
op.expedited ? -1 : 0 /* delay */,
0 /* flex */,
@@ -161,9 +174,38 @@
opsToRemove.add(op);
}
}
- for (SyncOperation op : opsToRemove) {
- remove(op);
+ for (SyncOperation op : opsToRemove) {
+ remove(op);
+ }
+ }
+
+ public boolean removeUidIfNeededLocked(int uid) {
+ ArrayList<SyncOperation> opsToRemove = null;
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.owningUid != uid) {
+ continue;
}
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(op.owningUid,
+ op.owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Removing sync " + op.owningUid + ":" + op
+ + " -- package not allowed to start");
+ continue;
+ }
+ } catch (RemoteException e) {
+ }
+ if (opsToRemove == null) {
+ opsToRemove = new ArrayList<SyncOperation>();
+ }
+ opsToRemove.add(op);
+ }
+ if (opsToRemove == null) {
+ return false;
+ }
+ for (SyncOperation op : opsToRemove) {
+ remove(op);
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index cca0c16..8266c08 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -27,6 +27,7 @@
import android.content.SyncInfo;
import android.content.SyncRequest;
import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -42,6 +43,7 @@
import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.ArrayMap;
import android.util.Xml;
@@ -227,14 +229,16 @@
public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
new EndPoint(null, null, UserHandle.USER_ALL);
final ComponentName service;
+ final int serviceUid; // -1 for "any"
final Account account;
final int userId;
final String provider;
final boolean target_service;
final boolean target_provider;
- public EndPoint(ComponentName service, int userId) {
+ public EndPoint(ComponentName service, int userId, int uid) {
this.service = service;
+ this.serviceUid = uid;
this.userId = userId;
this.account = null;
this.provider = null;
@@ -247,6 +251,7 @@
this.provider = provider;
this.userId = userId;
this.service = null;
+ this.serviceUid = -1;
this.target_service = false;
this.target_provider = true;
}
@@ -264,6 +269,11 @@
return false;
}
if (target_service && spec.target_service) {
+ if (serviceUid != spec.serviceUid
+ && serviceUid >= 0
+ && spec.serviceUid >= 0) {
+ return false;
+ }
return service.equals(spec.service);
} else if (target_provider && spec.target_provider) {
boolean accountsMatch;
@@ -290,8 +300,9 @@
.append("/")
.append(provider == null ? "ALL PDRS" : provider);
} else if (target_service) {
- sb.append(service.getPackageName() + "/")
- .append(service.getClassName());
+ service.appendShortString(sb);
+ sb.append(":");
+ UserHandle.formatUid(sb,serviceUid);
} else {
sb.append("invalid target");
}
@@ -737,7 +748,7 @@
synchronized (mAuthorities) {
if (cname != null) {
AuthorityInfo authority = getAuthorityLocked(
- new EndPoint(cname, userId),
+ new EndPoint(cname, userId, -1),
"get service active");
if (authority == null) {
return false;
@@ -749,7 +760,7 @@
}
public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
- setSyncableStateForEndPoint(new EndPoint(cname, userId), active ?
+ setSyncableStateForEndPoint(new EndPoint(cname, userId, -1), active ?
AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
}
@@ -2064,18 +2075,30 @@
new Account(accountName, accountType),
authorityName, userId);
} else {
- info = new EndPoint(
- new ComponentName(packageName, className),
- userId);
+ final ComponentName cname = new ComponentName(packageName, className);
+ android.content.pm.ServiceInfo sinfo = null;
+ try {
+ sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Not restoring sync " + cname
+ + " -- can't find service for user " + userId);
+ }
+ if (sinfo != null) {
+ info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
+ } else {
+ info = null;
+ }
}
- authority = getOrCreateAuthorityLocked(info, id, false);
- // If the version is 0 then we are upgrading from a file format that did not
- // know about periodic syncs. In that case don't clear the list since we
- // want the default, which is a daily periodic sync.
- // Otherwise clear out this default list since we will populate it later with
- // the periodic sync descriptions that are read from the configuration file.
- if (version > 0) {
- authority.periodicSyncs.clear();
+ if (info != null) {
+ authority = getOrCreateAuthorityLocked(info, id, false);
+ // If the version is 0 then we are upgrading from a file format that did not
+ // know about periodic syncs. In that case don't clear the list since we
+ // want the default, which is a daily periodic sync.
+ // Otherwise clear out this default list since we will populate it later with
+ // the periodic sync descriptions that are read from the configuration file.
+ if (version > 0) {
+ authority.periodicSyncs.clear();
+ }
}
}
if (authority != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 2b535b9..4d7df9c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -23,7 +23,9 @@
import java.util.List;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.app.AppGlobals;
+import android.app.IUidObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobService;
@@ -51,6 +53,8 @@
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;
@@ -83,6 +87,7 @@
static final int MSG_JOB_EXPIRED = 0;
static final int MSG_CHECK_JOB = 1;
+ static final int MSG_STOP_JOB = 2;
// Policy constants
/**
@@ -127,6 +132,7 @@
IBatteryStats mBatteryStats;
PowerManager mPowerManager;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
/**
* Set to true once we are allowed to run third party apps.
@@ -139,6 +145,11 @@
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.
*/
@@ -154,7 +165,7 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForUid(uidRemoved);
+ cancelJobsForUid(uidRemoved, true);
}
} else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -172,6 +183,21 @@
}
};
+ final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+ }
+
+ @Override public void onUidGone(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidActive(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidIdle(int uid) throws RemoteException {
+ cancelJobsForUid(uid, false);
+ }
+ };
+
@Override
public void onStartUser(int userHandle) {
mStartedUsers.add(userHandle);
@@ -194,6 +220,15 @@
public int schedule(JobInfo job, int uId) {
JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId());
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(uId,
+ job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ + " -- package not allowed to start");
+ return JobScheduler.RESULT_FAILURE;
+ }
+ } catch (RemoteException e) {
+ }
startTrackingJob(jobStatus);
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
return JobScheduler.RESULT_SUCCESS;
@@ -229,14 +264,26 @@
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
* @param uid Uid to check against for removal of a job.
+ * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
+ * whose apps are stopped.
*/
- public void cancelJobsForUid(int uid) {
+ public void cancelJobsForUid(int uid, boolean forceAll) {
List<JobStatus> jobsForUid;
synchronized (mJobs) {
jobsForUid = mJobs.getJobsByUid(uid);
}
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
+ if (!forceAll) {
+ String packageName = toRemove.getServiceComponent().getPackageName();
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
+ != ActivityManager.APP_START_MODE_DISABLED) {
+ continue;
+ }
+ } catch (RemoteException e) {
+ }
+ }
cancelJobImpl(toRemove);
}
}
@@ -268,6 +315,7 @@
mPendingJobs.remove(cancelled);
// Cancel if running.
stopJobOnServiceContextLocked(cancelled);
+ reportActive();
}
}
@@ -299,12 +347,39 @@
}
} 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>
@@ -348,12 +423,20 @@
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
+ try {
+ ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_IDLE);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mJobs) {
// Let's go!
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(
@@ -597,6 +680,9 @@
maybeQueueReadyJobsForExecutionLockedH();
}
break;
+ case MSG_STOP_JOB:
+ cancelJobImpl((JobStatus)message.obj);
+ break;
}
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
@@ -623,6 +709,7 @@
stopJobOnServiceContextLocked(job);
}
}
+ reportActive();
if (DEBUG) {
final int queuedJobs = mPendingJobs.size();
if (queuedJobs == 0) {
@@ -647,11 +734,22 @@
int idleCount = 0;
int backoffCount = 0;
int connectivityCount = 0;
- List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
+ List<JobStatus> runnableJobs = null;
ArraySet<JobStatus> jobs = mJobs.getJobs();
for (int i=0; i<jobs.size(); i++) {
JobStatus job = jobs.valueAt(i);
if (isReadyToBeExecutedLocked(job)) {
+ try {
+ if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
+ job.getJob().getService().getPackageName())
+ == ActivityManager.APP_START_MODE_DISABLED) {
+ Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ + job.getJob().toString() + " -- package not allowed to start");
+ mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
+ continue;
+ }
+ } catch (RemoteException e) {
+ }
if (job.getNumFailures() > 0) {
backoffCount++;
}
@@ -664,6 +762,9 @@
if (job.hasChargingConstraint()) {
chargingCount++;
}
+ if (runnableJobs == null) {
+ runnableJobs = new ArrayList<>();
+ }
runnableJobs.add(job);
} else if (isReadyToBeCancelledLocked(job)) {
stopJobOnServiceContextLocked(job);
@@ -673,7 +774,7 @@
idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
chargingCount >= MIN_CHARGING_COUNT ||
- runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
+ (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
}
@@ -685,6 +786,7 @@
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
}
}
+ reportActive();
if (DEBUG) {
Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
connectivityCount + " charging=" + chargingCount + " tot=" +
@@ -766,6 +868,7 @@
it.remove();
}
}
+ reportActive();
}
}
}
@@ -867,7 +970,7 @@
long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJobsForUid(uid);
+ JobSchedulerService.this.cancelJobsForUid(uid, true);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -948,6 +1051,7 @@
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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 41aea04..2ac0ba6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -459,7 +459,8 @@
updateScreenOn();
try {
- mActivityManager.registerUidObserver(mUidObserver);
+ mActivityManager.registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
@@ -541,6 +542,12 @@
removeUidStateLocked(uid);
}
}
+
+ @Override public void onUidActive(int uid) throws RemoteException {
+ }
+
+ @Override public void onUidIdle(int uid) throws RemoteException {
+ }
};
final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b84811f..fd1e9dd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,8 @@
package com.android.server.notification;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_PEEK;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -78,7 +80,6 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
-import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
@@ -2511,7 +2512,9 @@
// light
// release the light
boolean wasShowLights = mLights.remove(record.getKey());
- if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
+ if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
+ && ((record.getSuppressedVisualEffects()
+ & NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS) == 0)) {
mLights.add(record.getKey());
updateLightsLocked();
if (mUseAttentionLight) {
@@ -2701,6 +2704,11 @@
// let zen mode evaluate this record
private void applyZenModeLocked(NotificationRecord record) {
record.setIntercepted(mZenModeHelper.shouldIntercept(record));
+ if (record.isIntercepted()) {
+ int suppressed = (mZenModeHelper.shouldSuppressLight() ? SUPPRESSED_EFFECT_LIGHTS : 0)
+ | (mZenModeHelper.shouldSuppressPeek() ? SUPPRESSED_EFFECT_PEEK : 0);
+ record.setSuppressedVisualEffects(suppressed);
+ }
}
// lock on mNotificationList
@@ -3234,6 +3242,7 @@
ArrayList<String> keys = new ArrayList<String>(N);
ArrayList<String> interceptedKeys = new ArrayList<String>(N);
Bundle visibilityOverrides = new Bundle();
+ Bundle suppressedVisualEffects = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -3242,7 +3251,10 @@
keys.add(record.sbn.getKey());
if (record.isIntercepted()) {
interceptedKeys.add(record.sbn.getKey());
+
}
+ suppressedVisualEffects.putInt(
+ record.sbn.getKey(), record.getSuppressedVisualEffects());
if (record.getPackageVisibilityOverride()
!= NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
visibilityOverrides.putInt(record.sbn.getKey(),
@@ -3264,7 +3276,7 @@
String[] keysAr = keys.toArray(new String[keys.size()]);
String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
- speedBumpIndex);
+ speedBumpIndex, suppressedVisualEffects);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f37702c..2a7568d 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -83,6 +83,8 @@
private String mGlobalSortKey;
private int mPackageVisibility;
+ private int mSuppressedVisualEffects = 0;
+
@VisibleForTesting
public NotificationRecord(StatusBarNotification sbn, int score)
{
@@ -199,6 +201,7 @@
pw.println(prefix + " mCreationTimeMs=" + mCreationTimeMs);
pw.println(prefix + " mVisibleSinceMs=" + mVisibleSinceMs);
pw.println(prefix + " mUpdateTimeMs=" + mUpdateTimeMs);
+ pw.println(prefix + " mSuppressedVisualEffects= " + mSuppressedVisualEffects);
}
@@ -274,6 +277,14 @@
return mIntercept;
}
+ public void setSuppressedVisualEffects(int effects) {
+ mSuppressedVisualEffects = effects;
+ }
+
+ public int getSuppressedVisualEffects() {
+ return mSuppressedVisualEffects;
+ }
+
public boolean isCategory(String category) {
return Objects.equals(getNotification().category, category);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a1f8c41..dbdc3f4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -138,6 +138,18 @@
}
}
+ public boolean shouldSuppressLight() {
+ synchronized (mConfig) {
+ return !mConfig.allowLights;
+ }
+ }
+
+ public boolean shouldSuppressPeek() {
+ synchronized (mConfig) {
+ return !mConfig.allowPeek;
+ }
+ }
+
public void addCallback(Callback callback) {
mCallbacks.add(callback);
}
@@ -394,11 +406,11 @@
return;
}
pw.printf("allow(calls=%s,callsFrom=%s,repeatCallers=%s,messages=%s,messagesFrom=%s,"
- + "events=%s,reminders=%s)\n",
+ + "events=%s,reminders=%s,lights=%s,peek=%s)\n",
config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
config.allowRepeatCallers, config.allowMessages,
ZenModeConfig.sourceToString(config.allowMessagesFrom),
- config.allowEvents, config.allowReminders);
+ config.allowEvents, config.allowReminders, config.allowLights, config.allowPeek);
pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
if (config.automaticRules.isEmpty()) return;
final int N = config.automaticRules.size();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c152514..992919e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1375,7 +1375,9 @@
// Now that we successfully installed the package, grant runtime
// permissions if requested before broadcasting the install.
if ((args.installFlags
- & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) {
+ & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && res.pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M) {
grantRequestedRuntimePermissions(res.pkg, args.user.getIdentifier(),
args.installGrantPermissions);
}
@@ -1414,18 +1416,18 @@
}
}
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 +1958,7 @@
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
@@ -3568,6 +3569,11 @@
return;
}
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+ return;
+ }
+
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
@@ -9371,8 +9377,8 @@
}
};
- 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 +9408,7 @@
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 +9600,7 @@
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 +12910,11 @@
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 +12949,14 @@
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 +14656,12 @@
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 +14849,23 @@
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 +14964,9 @@
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 +15033,29 @@
} 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 +15222,28 @@
}
}
- 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 +15690,7 @@
}
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 +16395,26 @@
}
/** 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 +16464,18 @@
}
/** 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 d7176fd..2cedc9c 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.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.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.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 @@
final IPackageManager mInterface;
final private WeakHashMap<String, Resources> mResourceCache =
new WeakHashMap<String, Resources>();
+ int mTargetUser;
PackageManagerShellCommand(PackageManagerService service) {
mInterface = service;
@@ -97,6 +101,12 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
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 5d8b1d2..903d12b 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 @@
// 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 @@
}
}
+ 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 de14739..1d299d7 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 @@
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 3a1d2de..ab0b182 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 @@
*
* 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 @@
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 @@
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+ @GuardedBy("mGuestRestrictions")
private final Bundle mGuestRestrictions = new Bundle();
/**
@@ -226,6 +225,7 @@
@GuardedBy("mUsersLock")
private int[] mUserIds;
+ @GuardedBy("mPackagesLock")
private int mNextSerialNumber;
private int mUserVersion = 0;
@@ -245,11 +245,9 @@
}
}
- /**
- * 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 @@
* 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 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 @@
* 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 @@
@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 @@
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 @@
}
}
- private void readUserListLILP() {
+ private void readUserListLP() {
if (!mUserListFile.exists()) {
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
FileInputStream fis = null;
@@ -1015,7 +1004,7 @@
if (type != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
@@ -1036,7 +1025,7 @@
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 @@
&& 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 @@
}
}
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 @@
+ 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 @@
updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLILP();
+ writeUserListLP();
writeUserLP(system);
}
@@ -1247,8 +1231,7 @@
* <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 @@
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 @@
}
}
- private UserInfo readUserLILP(int id) {
+ private UserInfo readUserLP(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -1468,8 +1453,7 @@
}
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 @@
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;
+ synchronized (mPackagesLock) {
+ UserInfo parent = null;
+ if (parentId != UserHandle.USER_NULL) {
+ synchronized (mUsersLock) {
+ parent = getUserInfoLU(parentId);
}
- if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more managed profiles for user " + 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;
+ }
+ // 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;
}
- 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.
+ 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 we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUser() != null) {
- 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 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;
- }
- 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;
- }
- }
- 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 @@
// Clean up any ActivityManager state
LocalServices.getService(ActivityManagerInternal.class)
.onUserRemoved(userHandle);
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- removeUserStateLILP(userHandle);
- }
- }
+ removeUserState(userHandle);
}
}.start();
}
@@ -1798,10 +1772,10 @@
}
}
- 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 @@
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 @@
* @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 @@
* 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 @@
}
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 d713751..c246609 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 @@
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 @@
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();
+ 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);
+ }
- 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);
}
- mOldMagnifiedBounds.set(magnifiedBounds);
+ if (availableChanged) {
+ mOldAvailableBounds.set(availableBounds);
+ }
+
+ 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 @@
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 89f5658..943c9ed 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 @@
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 @@
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 @@
@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 @@
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 @@
} 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 @@
}
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 2905269..dfd01ef 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 @@
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 @@
// 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 @@
anim.setBackgroundColor(0);
}
if (mClearProlongedAnimation) {
- mProlongAnimation = false;
+ mProlongAnimation = PROLONG_ANIMATION_DISABLED;
} else {
mClearProlongedAnimation = true;
}
@@ -266,6 +270,10 @@
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 @@
"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 @@
}
}
- 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 425ff9b..3f4eaac 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 @@
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 e264c43..328c043 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 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 @@
* 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 @@
}
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 2be7ab8..4926352 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 3c3123f..1f351cb 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 @@
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 @@
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 92eacd6..a9bd71f 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_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 @@
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 @@
}
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 @@
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 @@
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;
- }
-
- mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
- displayContent.getDisplayId(), displayInfo);
-
- displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
+ 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);
+
+ displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}
@@ -8060,10 +8064,8 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
} 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 @@
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 @@
* 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 @@
+ " 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 @@
}
@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 673c21f..c6f7f4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,11 +16,57 @@
package com.android.server.wm;
+import com.android.server.input.InputWindowHandle;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+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;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+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;
@@ -36,8 +82,6 @@
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;
@@ -46,47 +90,6 @@
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-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.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.view.IApplicationToken;
-import android.view.IWindow;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
class WindowList extends ArrayList<WindowState> {
}
@@ -106,8 +109,6 @@
// 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 @@
&& 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 @@
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 @@
mTmpRect.inset(-delta, -delta);
}
region.set(mTmpRect);
+ cropRegionToStackBoundsIfNeeded(region);
} else {
// Not modal or full screen modal
getTouchableRegion(region);
@@ -1772,26 +1772,41 @@
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 f7805b1..6faf3a7 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.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 @@
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 @@
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 @@
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 @@
mAnimating = false;
mKeyguardGoingAwayAnimation = false;
- mAnimatingMove = false;
mLocalAnimating = false;
if (mAnimation != null) {
mAnimation.cancel();
@@ -773,7 +767,14 @@
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 @@
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 @@
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 ae96658..979f55b 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_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.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 @@
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/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c611503..c743f24 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6706,11 +6706,28 @@
@Override
public boolean isProvisioningAllowed(String action) {
- if (mOwners.hasDeviceOwner()) {
- return false;
- }
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
+ if (mOwners.hasDeviceOwner()) {
+ if (!mInjector.userManagerIsSplitSystemUser()) {
+ // Only split-system-user systems support managed-profiles in combination with
+ // device-owner.
+ return false;
+ }
+ if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
+ // Only system device-owner supports managed-profiles. Non-system device-owner
+ // doesn't.
+ return false;
+ }
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // Managed-profiles cannot be setup on the system user, only regular users.
+ return false;
+ }
+ }
+ if (getProfileOwner(callingUserId) != null) {
+ // Managed user cannot have a managed profile.
+ return false;
+ }
try {
if (!mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
return false;
@@ -6730,7 +6747,7 @@
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
return isDeviceOwnerProvisioningAllowed(callingUserId);
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
- if (!UserManager.isSplitSystemUser()) {
+ if (!mInjector.userManagerIsSplitSystemUser()) {
// ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
return false;
}
@@ -6739,7 +6756,7 @@
}
return true;
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action)) {
- if (!UserManager.isSplitSystemUser()) {
+ if (!mInjector.userManagerIsSplitSystemUser()) {
// ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
return false;
}
@@ -6749,6 +6766,9 @@
}
private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
+ if (mOwners.hasDeviceOwner()) {
+ return false;
+ }
if (getProfileOwner(callingUserId) != null) {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index b0296a0..c174a92 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -61,7 +61,7 @@
b2.putBoolean("b2", true);
SyncOperation op1 = new SyncOperation(account1, 0,
- 1,
+ 1, "foo", 0,
SyncOperation.REASON_PERIODIC,
"authority1",
b1,
@@ -73,7 +73,7 @@
// Same as op1 but different time infos
SyncOperation op2 = new SyncOperation(account1, 0,
- 1,
+ 1, "foo", 0,
SyncOperation.REASON_PERIODIC,
"authority1",
b1,
@@ -85,7 +85,7 @@
// Same as op1 but different authority
SyncOperation op3 = new SyncOperation(account1, 0,
- 1,
+ 1, "foo", 0,
SyncOperation.REASON_PERIODIC,
"authority2",
b1,
@@ -97,7 +97,7 @@
// Same as op1 but different account
SyncOperation op4 = new SyncOperation(account2, 0,
- 1,
+ 1, "foo", 0,
SyncOperation.REASON_PERIODIC,
"authority1",
b1,
@@ -109,7 +109,7 @@
// Same as op1 but different bundle
SyncOperation op5 = new SyncOperation(account1, 0,
- 1,
+ 1, "foo", 0,
SyncOperation.REASON_PERIODIC,
"authority1",
b2,
@@ -131,21 +131,21 @@
long soonFlex = 50;
long after = 1500;
long afterFlex = 100;
- SyncOperation op1 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ SyncOperation op1 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
"authority1", mEmpty, soon, soonFlex, mUnimportantLong, mUnimportantLong, true);
// Interval disjoint from and after op1.
- SyncOperation op2 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ SyncOperation op2 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
"authority1", mEmpty, after, afterFlex, mUnimportantLong, mUnimportantLong, true);
// Interval equivalent to op1, but expedited.
Bundle b2 = new Bundle();
b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
- SyncOperation op3 = new SyncOperation(mDummy, 0, 0, 0,
+ SyncOperation op3 = new SyncOperation(mDummy, 0, 0, "foo", 0, 0,
"authority1", b2, -1, soonFlex, mUnimportantLong, mUnimportantLong, true);
// Interval overlaps but not equivalent to op1.
- SyncOperation op4 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ SyncOperation op4 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
"authority1", mEmpty, soon + 100, soonFlex + 100, mUnimportantLong, mUnimportantLong, true);
assertTrue(op1.compareTo(op2) == -1);
@@ -165,7 +165,8 @@
Bundle withExpedited = new Bundle();
withExpedited.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
- SyncOperation op = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_USER_START,
+ SyncOperation op = new SyncOperation(mDummy, 0, 0, "foo", 0,
+ SyncOperation.REASON_USER_START,
mAuthority, withExpedited, fiveSecondsFromNow, twoSecondsFlex,
eightSeconds /* backoff */, fourSeconds /* delayUntil */, true);
// Create another sync op to be rerun in 5 minutes.
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index ae1967e..b22eb53 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -41,8 +41,6 @@
import java.io.FileOutputStream;
import java.util.List;
-import com.android.server.content.SyncStorageEngine.EndPoint;
-
public class SyncStorageEngineTest extends AndroidTestCase {
protected Account account1;
@@ -96,7 +94,7 @@
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
new TestContext(mockResolver, getContext()));
long time0 = 1000;
- SyncOperation op = new SyncOperation(account, 0,
+ SyncOperation op = new SyncOperation(account, 0, 0, "foo",
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_LOCAL,
authority,
@@ -112,7 +110,7 @@
@MediumTest
public void testAppendPending() throws Exception {
SyncOperation sop = new SyncOperation(account1,
- DEFAULT_USER,
+ DEFAULT_USER, 0, "foo",
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
@@ -140,19 +138,19 @@
*/
public void testWritePendingOperationsLocked() throws Exception {
SyncOperation sop = new SyncOperation(account1,
- DEFAULT_USER,
+ DEFAULT_USER, 0, "foo",
SyncOperation.REASON_IS_SYNCABLE,
SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
1000L /* runtime */, 57L /* flex */, 0 /* backoff */, 0 /* delayuntil */,
true /* expedited */);
SyncOperation sop1 = new SyncOperation(account2,
- DEFAULT_USER,
+ DEFAULT_USER, 0, "foo",
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_LOCAL, authority1, defaultBundle,
0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
false /* expedited */);
SyncOperation deleted = new SyncOperation(account2,
- DEFAULT_USER,
+ DEFAULT_USER, 0, "foo",
SyncOperation.REASON_SYNC_AUTO,
SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
@@ -456,14 +454,14 @@
// Test service component read
List<PeriodicSync> syncs = engine.getPeriodicSyncs(
- new SyncStorageEngine.EndPoint(syncService1, 0));
+ new SyncStorageEngine.EndPoint(syncService1, 0, 0));
assertEquals(1, syncs.size());
assertEquals(true, engine.getIsTargetServiceActive(syncService1, 0));
}
@SmallTest
public void testComponentSettings() throws Exception {
- EndPoint target1 = new EndPoint(syncService1, 0);
+ EndPoint target1 = new EndPoint(syncService1, 0, 0);
engine.updateOrAddPeriodicSync(target1, dayPoll, dayFuzz, Bundle.EMPTY);
engine.setIsTargetServiceActive(target1.service, 0, true);
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index f2a1878..d292f62 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -747,7 +747,13 @@
}
}
- 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 @@
}
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 @@
}
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 d3c3c10..b1a4c7d 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 @@
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 @@
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 851edc8..34daa66 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -34,8 +34,9 @@
*
* 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 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 7de8f41..3d2a6e1 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -90,6 +90,26 @@
&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 9a46bcb..721bf5b 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -215,10 +215,13 @@
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 93f2dc6f..97be774 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -50,7 +50,7 @@
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 @@
}
}
- mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) };
+ mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
return true;
}
@@ -640,8 +640,7 @@
}
}
- 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 @@
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 @@
std::unique_ptr<TableMerger> mTableMerger;
struct FileToProcess {
- Source source;
ResourceFile file;
+ Source source;
};
- std::map<ResourceName, FileToProcess> mFilesToProcess;
+
+ 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::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 @@
"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 @@
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 c70531b..a4cb596 100644
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -80,7 +80,7 @@
return;
}
- DiagMessage errorMsg;
+ DiagMessage errorMsg(reference->getSource());
errorMsg << "reference to " << reference->name.value();
if (realName) {
errorMsg << " (aka " << realName.value() << ")";
@@ -92,7 +92,7 @@
}
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 147b9bf..caab9b8 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -33,6 +33,7 @@
private:
IAaptContext* mContext;
ISymbolTable* mSymbols;
+ Source mSource;
std::set<int>* mSdkLevelsFound;
ReferenceLinkerVisitor mReferenceLinkerVisitor;
bool mError = false;
@@ -40,13 +41,14 @@
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 @@
!(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 @@
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 @@
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 9a8b263..bb33ea7 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -77,16 +77,8 @@
}
-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 @@
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 @@
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 0d17e84..3048334 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -304,6 +304,11 @@
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 80552a5..324afb3 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -229,11 +229,12 @@
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 @@
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 @@
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 @@
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 9db9fb7..9208e07 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -101,6 +101,15 @@
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/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
index e206d70..f758959 100644
--- a/tools/layoutlib/.idea/encodings.xml
+++ b/tools/layoutlib/.idea/encodings.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+ <file url="PROJECT" charset="UTF-8" />
+ </component>
+</project>
\ No newline at end of file
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 868c6d3..b67afeb 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 @@
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 @@
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 e611ea4..6e42391 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -737,7 +737,9 @@
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 c26ca6e..5534cad 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -168,6 +168,22 @@
* 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 @@
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 @@
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++) {