Merge "Unlock newly created managed profiles." into nyc-dev
diff --git a/Android.mk b/Android.mk
index fc9c319..ae5d67e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -244,6 +244,8 @@
core/java/android/service/notification/IConditionListener.aidl \
core/java/android/service/notification/IConditionProvider.aidl \
core/java/android/service/vr/IVrListener.aidl \
+ core/java/android/service/vr/IVrManager.aidl \
+ core/java/android/service/vr/IVrStateCallbacks.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
core/java/android/print/IPrinterDiscoveryObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \
diff --git a/api/current.txt b/api/current.txt
index dfdff24..9bd20ff 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4979,7 +4979,7 @@
field public int ledARGB;
field public int ledOffMS;
field public int ledOnMS;
- field public int number;
+ field public deprecated int number;
field public int priority;
field public android.app.Notification publicVersion;
field public android.net.Uri sound;
@@ -5026,11 +5026,13 @@
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
method public java.lang.CharSequence getCancelLabel();
method public java.lang.CharSequence getConfirmLabel();
+ method public boolean getHintContentIntentLaunchesActivity();
method public java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
@@ -5072,7 +5074,7 @@
method public android.app.Notification.Builder setChronometerCountsDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
- method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
@@ -5089,7 +5091,7 @@
method public android.app.Notification.Builder setLargeIcon(android.graphics.drawable.Icon);
method public android.app.Notification.Builder setLights(int, int, int);
method public android.app.Notification.Builder setLocalOnly(boolean);
- method public android.app.Notification.Builder setNumber(int);
+ method public deprecated android.app.Notification.Builder setNumber(int);
method public android.app.Notification.Builder setOngoing(boolean);
method public android.app.Notification.Builder setOnlyAlertOnce(boolean);
method public android.app.Notification.Builder setPriority(int);
@@ -5207,6 +5209,7 @@
method public android.app.PendingIntent getDisplayIntent();
method public int getGravity();
method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
method public boolean getHintHideIcon();
method public int getHintScreenTimeout();
method public boolean getHintShowBackgroundOnly();
@@ -5222,6 +5225,7 @@
method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent);
method public android.app.Notification.WearableExtender setGravity(int);
method public android.app.Notification.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public android.app.Notification.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.WearableExtender setHintHideIcon(boolean);
method public android.app.Notification.WearableExtender setHintScreenTimeout(int);
method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean);
@@ -7885,6 +7889,7 @@
method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
method public void notifyChange(android.net.Uri, android.database.ContentObserver);
method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+ method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -7915,6 +7920,8 @@
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+ field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
field public static final java.lang.String SCHEME_CONTENT = "content";
field public static final java.lang.String SCHEME_FILE = "file";
@@ -34685,6 +34692,7 @@
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
+ method public void onListenerDisconnected();
method public void onListenerHintsChanged(int);
method public void onNotificationPosted(android.service.notification.StatusBarNotification);
method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
@@ -46435,21 +46443,21 @@
ctor public DatePicker(android.content.Context, android.util.AttributeSet);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int, int);
- method public android.widget.CalendarView getCalendarView();
- method public boolean getCalendarViewShown();
+ method public deprecated android.widget.CalendarView getCalendarView();
+ method public deprecated boolean getCalendarViewShown();
method public int getDayOfMonth();
method public int getFirstDayOfWeek();
method public long getMaxDate();
method public long getMinDate();
method public int getMonth();
- method public boolean getSpinnersShown();
+ method public deprecated boolean getSpinnersShown();
method public int getYear();
method public void init(int, int, int, android.widget.DatePicker.OnDateChangedListener);
- method public void setCalendarViewShown(boolean);
+ method public deprecated void setCalendarViewShown(boolean);
method public void setFirstDayOfWeek(int);
method public void setMaxDate(long);
method public void setMinDate(long);
- method public void setSpinnersShown(boolean);
+ method public deprecated void setSpinnersShown(boolean);
method public void updateDate(int, int, int);
}
@@ -50022,6 +50030,9 @@
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
method public static int hashCode(boolean);
+ method public static boolean logicalAnd(boolean, boolean);
+ method public static boolean logicalOr(boolean, boolean);
+ method public static boolean logicalXor(boolean, boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -50985,6 +50996,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -50994,12 +51007,20 @@
method public static float copySign(float, float);
method public static double cos(double);
method public static double cosh(double);
+ method public static int decrementExact(int);
+ method public static long decrementExact(long);
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
+ method public static int incrementExact(int);
+ method public static long incrementExact(long);
method public static double log(double);
method public static double log10(double);
method public static double log1p(double);
@@ -51011,8 +51032,14 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
+ method public static int negateExact(int);
+ method public static long negateExact(long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -51027,9 +51054,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
@@ -51313,6 +51343,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -51325,6 +51357,10 @@
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
@@ -51339,8 +51375,12 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -51355,9 +51395,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
diff --git a/api/system-current.txt b/api/system-current.txt
index 96b1e28..bfbe4ec 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5112,7 +5112,7 @@
field public int ledARGB;
field public int ledOffMS;
field public int ledOnMS;
- field public int number;
+ field public deprecated int number;
field public int priority;
field public android.app.Notification publicVersion;
field public android.net.Uri sound;
@@ -5159,11 +5159,13 @@
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
method public java.lang.CharSequence getCancelLabel();
method public java.lang.CharSequence getConfirmLabel();
+ method public boolean getHintContentIntentLaunchesActivity();
method public java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
@@ -5205,7 +5207,7 @@
method public android.app.Notification.Builder setChronometerCountsDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
- method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
@@ -5222,7 +5224,7 @@
method public android.app.Notification.Builder setLargeIcon(android.graphics.drawable.Icon);
method public android.app.Notification.Builder setLights(int, int, int);
method public android.app.Notification.Builder setLocalOnly(boolean);
- method public android.app.Notification.Builder setNumber(int);
+ method public deprecated android.app.Notification.Builder setNumber(int);
method public android.app.Notification.Builder setOngoing(boolean);
method public android.app.Notification.Builder setOnlyAlertOnce(boolean);
method public android.app.Notification.Builder setPriority(int);
@@ -5340,6 +5342,7 @@
method public android.app.PendingIntent getDisplayIntent();
method public int getGravity();
method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
method public boolean getHintHideIcon();
method public int getHintScreenTimeout();
method public boolean getHintShowBackgroundOnly();
@@ -5355,6 +5358,7 @@
method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent);
method public android.app.Notification.WearableExtender setGravity(int);
method public android.app.Notification.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public android.app.Notification.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.WearableExtender setHintHideIcon(boolean);
method public android.app.Notification.WearableExtender setHintScreenTimeout(int);
method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean);
@@ -8183,6 +8187,7 @@
method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
method public void notifyChange(android.net.Uri, android.database.ContentObserver);
method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+ method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -8213,6 +8218,8 @@
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+ field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
field public static final java.lang.String SCHEME_CONTENT = "content";
field public static final java.lang.String SCHEME_FILE = "file";
@@ -37151,6 +37158,7 @@
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
+ method public void onListenerDisconnected();
method public void onListenerHintsChanged(int);
method public void onNotificationPosted(android.service.notification.StatusBarNotification);
method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
@@ -49532,21 +49540,21 @@
ctor public DatePicker(android.content.Context, android.util.AttributeSet);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int, int);
- method public android.widget.CalendarView getCalendarView();
- method public boolean getCalendarViewShown();
+ method public deprecated android.widget.CalendarView getCalendarView();
+ method public deprecated boolean getCalendarViewShown();
method public int getDayOfMonth();
method public int getFirstDayOfWeek();
method public long getMaxDate();
method public long getMinDate();
method public int getMonth();
- method public boolean getSpinnersShown();
+ method public deprecated boolean getSpinnersShown();
method public int getYear();
method public void init(int, int, int, android.widget.DatePicker.OnDateChangedListener);
- method public void setCalendarViewShown(boolean);
+ method public deprecated void setCalendarViewShown(boolean);
method public void setFirstDayOfWeek(int);
method public void setMaxDate(long);
method public void setMinDate(long);
- method public void setSpinnersShown(boolean);
+ method public deprecated void setSpinnersShown(boolean);
method public void updateDate(int, int, int);
}
@@ -53119,6 +53127,9 @@
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
method public static int hashCode(boolean);
+ method public static boolean logicalAnd(boolean, boolean);
+ method public static boolean logicalOr(boolean, boolean);
+ method public static boolean logicalXor(boolean, boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -54082,6 +54093,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -54091,12 +54104,20 @@
method public static float copySign(float, float);
method public static double cos(double);
method public static double cosh(double);
+ method public static int decrementExact(int);
+ method public static long decrementExact(long);
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
+ method public static int incrementExact(int);
+ method public static long incrementExact(long);
method public static double log(double);
method public static double log10(double);
method public static double log1p(double);
@@ -54108,8 +54129,14 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
+ method public static int negateExact(int);
+ method public static long negateExact(long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -54124,9 +54151,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
@@ -54410,6 +54440,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -54422,6 +54454,10 @@
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
@@ -54436,8 +54472,12 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -54452,9 +54492,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
diff --git a/api/test-current.txt b/api/test-current.txt
index 8a745bf..85badae 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4979,7 +4979,7 @@
field public int ledARGB;
field public int ledOffMS;
field public int ledOnMS;
- field public int number;
+ field public deprecated int number;
field public int priority;
field public android.app.Notification publicVersion;
field public android.net.Uri sound;
@@ -5026,11 +5026,13 @@
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
method public java.lang.CharSequence getCancelLabel();
method public java.lang.CharSequence getConfirmLabel();
+ method public boolean getHintContentIntentLaunchesActivity();
method public java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
@@ -5072,7 +5074,7 @@
method public android.app.Notification.Builder setChronometerCountsDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
- method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setContentText(java.lang.CharSequence);
method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence);
@@ -5089,7 +5091,7 @@
method public android.app.Notification.Builder setLargeIcon(android.graphics.drawable.Icon);
method public android.app.Notification.Builder setLights(int, int, int);
method public android.app.Notification.Builder setLocalOnly(boolean);
- method public android.app.Notification.Builder setNumber(int);
+ method public deprecated android.app.Notification.Builder setNumber(int);
method public android.app.Notification.Builder setOngoing(boolean);
method public android.app.Notification.Builder setOnlyAlertOnce(boolean);
method public android.app.Notification.Builder setPriority(int);
@@ -5207,6 +5209,7 @@
method public android.app.PendingIntent getDisplayIntent();
method public int getGravity();
method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
method public boolean getHintHideIcon();
method public int getHintScreenTimeout();
method public boolean getHintShowBackgroundOnly();
@@ -5222,6 +5225,7 @@
method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent);
method public android.app.Notification.WearableExtender setGravity(int);
method public android.app.Notification.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public android.app.Notification.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.app.Notification.WearableExtender setHintHideIcon(boolean);
method public android.app.Notification.WearableExtender setHintScreenTimeout(int);
method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean);
@@ -7890,6 +7894,7 @@
method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
method public void notifyChange(android.net.Uri, android.database.ContentObserver);
method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+ method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -7920,6 +7925,8 @@
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+ field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
field public static final java.lang.String SCHEME_CONTENT = "content";
field public static final java.lang.String SCHEME_FILE = "file";
@@ -34757,6 +34764,7 @@
method public android.os.IBinder onBind(android.content.Intent);
method public void onInterruptionFilterChanged(int);
method public void onListenerConnected();
+ method public void onListenerDisconnected();
method public void onListenerHintsChanged(int);
method public void onNotificationPosted(android.service.notification.StatusBarNotification);
method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
@@ -46509,21 +46517,21 @@
ctor public DatePicker(android.content.Context, android.util.AttributeSet);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int, int);
- method public android.widget.CalendarView getCalendarView();
- method public boolean getCalendarViewShown();
+ method public deprecated android.widget.CalendarView getCalendarView();
+ method public deprecated boolean getCalendarViewShown();
method public int getDayOfMonth();
method public int getFirstDayOfWeek();
method public long getMaxDate();
method public long getMinDate();
method public int getMonth();
- method public boolean getSpinnersShown();
+ method public deprecated boolean getSpinnersShown();
method public int getYear();
method public void init(int, int, int, android.widget.DatePicker.OnDateChangedListener);
- method public void setCalendarViewShown(boolean);
+ method public deprecated void setCalendarViewShown(boolean);
method public void setFirstDayOfWeek(int);
method public void setMaxDate(long);
method public void setMinDate(long);
- method public void setSpinnersShown(boolean);
+ method public deprecated void setSpinnersShown(boolean);
method public void updateDate(int, int, int);
}
@@ -50097,6 +50105,9 @@
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
method public static int hashCode(boolean);
+ method public static boolean logicalAnd(boolean, boolean);
+ method public static boolean logicalOr(boolean, boolean);
+ method public static boolean logicalXor(boolean, boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -51060,6 +51071,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -51069,12 +51082,20 @@
method public static float copySign(float, float);
method public static double cos(double);
method public static double cosh(double);
+ method public static int decrementExact(int);
+ method public static long decrementExact(long);
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
+ method public static int incrementExact(int);
+ method public static long incrementExact(long);
method public static double log(double);
method public static double log10(double);
method public static double log1p(double);
@@ -51086,8 +51107,14 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
+ method public static int negateExact(int);
+ method public static long negateExact(long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -51102,9 +51129,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
@@ -51388,6 +51418,8 @@
method public static float abs(float);
method public static double abs(double);
method public static double acos(double);
+ method public static int addExact(int, int);
+ method public static long addExact(long, long);
method public static double asin(double);
method public static double atan(double);
method public static double atan2(double, double);
@@ -51400,6 +51432,10 @@
method public static double exp(double);
method public static double expm1(double);
method public static double floor(double);
+ method public static int floorDiv(int, int);
+ method public static long floorDiv(long, long);
+ method public static int floorMod(int, int);
+ method public static long floorMod(long, long);
method public static int getExponent(float);
method public static int getExponent(double);
method public static double hypot(double, double);
@@ -51414,8 +51450,12 @@
method public static long min(long, long);
method public static float min(float, float);
method public static double min(double, double);
+ method public static int multiplyExact(int, int);
+ method public static long multiplyExact(long, long);
method public static double nextAfter(double, double);
method public static float nextAfter(float, double);
+ method public static double nextDown(double);
+ method public static float nextDown(float);
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
@@ -51430,9 +51470,12 @@
method public static double sin(double);
method public static double sinh(double);
method public static double sqrt(double);
+ method public static int subtractExact(int, int);
+ method public static long subtractExact(long, long);
method public static double tan(double);
method public static double tanh(double);
method public static double toDegrees(double);
+ method public static int toIntExact(long);
method public static double toRadians(double);
method public static double ulp(double);
method public static float ulp(float);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 13e8e75..4fca69a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -206,7 +206,7 @@
}
results.putAll(mPerfMetrics);
}
- if (mUiAutomation != null) {
+ if ((mUiAutomation != null) && !mUiAutomation.isDestroyed()) {
mUiAutomation.disconnect();
mUiAutomation = null;
}
@@ -1834,7 +1834,7 @@
}
/**
- * Gets the {@link UiAutomation} instance.
+ * Gets the {@link UiAutomation} instance with no flags set.
* <p>
* <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
* work across application boundaries while the APIs exposed by the instrumentation
@@ -1848,25 +1848,21 @@
* {@link Instrumentation} APIs. Using both APIs at the same time is not
* a mistake by itself but a client has to be aware of the APIs limitations.
* </p>
- * @return The UI automation instance. If none exists, a new one is created with no flags set.
+ * <p>
+ * Equivalent to {@code getUiAutomation(0)}. If a {@link UiAutomation} exists with different
+ * flags, the flags on that instance will be changed, and then it will be returned.
+ * </p>
+ * @return The UI automation instance.
*
* @see UiAutomation
*/
public UiAutomation getUiAutomation() {
- if ((mUiAutomation == null) || (mUiAutomation.isDestroyed())) {
- return getUiAutomation(0);
- }
- return mUiAutomation;
+ return getUiAutomation(0);
}
/**
* Gets the {@link UiAutomation} instance with flags set.
* <p>
- * <strong>Note:</strong> Only one UiAutomation can be obtained. Calling this method
- * twice with different flags will fail unless the UiAutomation obtained in the first call
- * is released with {@link UiAutomation#destroy()}.
- * </p>
- * <p>
* <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
* work across application boundaries while the APIs exposed by the instrumentation
* do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
@@ -1879,6 +1875,10 @@
* {@link Instrumentation} APIs. Using both APIs at the same time is not
* a mistake by itself but a client has to be aware of the APIs limitations.
* </p>
+ * <p>
+ * If a {@link UiAutomation} exists with different flags, the flags on that instance will be
+ * changed, and then it will be returned.
+ * </p>
*
* @param flags The flags to be passed to the UiAutomation, for example
* {@link UiAutomation#FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES}.
@@ -1888,17 +1888,19 @@
* @see UiAutomation
*/
public UiAutomation getUiAutomation(@UiAutomationFlags int flags) {
+ boolean mustCreateNewAutomation = (mUiAutomation == null) || (mUiAutomation.isDestroyed());
+
if (mUiAutomationConnection != null) {
- if ((mUiAutomation == null) || (mUiAutomation.isDestroyed())) {
+ if (!mustCreateNewAutomation && (mUiAutomation.getFlags() == flags)) {
+ return mUiAutomation;
+ }
+ if (mustCreateNewAutomation) {
mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
mUiAutomationConnection);
- mUiAutomation.connect(flags);
} else {
- if (mUiAutomation.getFlags() != flags) {
- throw new RuntimeException(
- "Cannot get a UiAutomation with different flags from the existing one");
- }
+ mUiAutomation.disconnect();
}
+ mUiAutomation.connect(flags);
return mUiAutomation;
}
return null;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8423de8..2e4a8c6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -174,6 +174,9 @@
* <li>Notification of an ongoing countdown timer should be stamped with the timer's end time.
* </ul>
*
+ * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown
+ * anymore by default and must be opted into by using
+ * {@link android.app.Notification.Builder#setShowWhen(boolean)}
*/
public long when;
@@ -206,6 +209,8 @@
* {@link Notification.Builder} has displayed the number in the expanded notification view.
*
* If the number is 0 or negative, it is never shown.
+ *
+ * @deprecated this number is not shown anymore
*/
public int number;
@@ -1205,6 +1210,7 @@
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
+ private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
@@ -1367,6 +1373,30 @@
public CharSequence getCancelLabel() {
return mCancelLabel;
}
+
+ /**
+ * Set a hint that this Action will launch an {@link Activity} directly, telling the
+ * platform that it can generate the appropriate transitions.
+ * @param hintLaunchesActivity {@code true} if the content intent will launch
+ * an activity and transitions should be generated, false otherwise.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintContentIntentLaunchesActivity(
+ boolean hintLaunchesActivity) {
+ setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
+ return this;
+ }
+
+ /**
+ * Get a hint that this Action will launch an {@link Activity} directly, telling the
+ * platform that it can generate the appropriate transitions
+ * @return {@code true} if the content intent will launch an activity and transitions
+ * should be generated, false otherwise. The default value is {@code false} if this was
+ * never set.
+ */
+ public boolean getHintContentIntentLaunchesActivity() {
+ return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
+ }
}
}
@@ -2133,7 +2163,9 @@
if (toAdopt == null) {
mN = new Notification();
- mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
+ if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
+ }
mN.priority = PRIORITY_DEFAULT;
mN.visibility = VISIBILITY_PRIVATE;
} else {
@@ -2183,8 +2215,10 @@
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
- * It will be shown in the notification content view by default; use
- * {@link #setShowWhen(boolean) setShowWhen} to control this.
+ *
+ * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
+ * shown anymore by default and must be opted into by using
+ * {@link android.app.Notification.Builder#setShowWhen(boolean)}
*
* @see Notification#when
*/
@@ -2196,6 +2230,8 @@
/**
* Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
* in the content view.
+ * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
+ * {@code false}. For earlier apps, the default is {@code true}.
*/
public Builder setShowWhen(boolean show) {
mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
@@ -2304,9 +2340,22 @@
}
/**
- * Set the third line of text in the platform notification template.
- * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the
- * same location in the standard template.
+ * This provides some additional information that is displayed in the notification. No
+ * guarantees are given where exactly it is displayed.
+ *
+ * <p>This information should only be provided if it provides an essential
+ * benefit to the understanding of the notification. The more text you provide the
+ * less readable it becomes. For example, an email client should only provide the account
+ * name here if more than one email account has been added.</p>
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the
+ * notification header area.
+ *
+ * On Android versions before {@link android.os.Build.VERSION_CODES#N}
+ * this will be shown in the third line of text in the platform notification template.
+ * You should not be using {@link #setProgress(int, int, boolean)} at the
+ * same time on those versions; they occupy the same place.
+ * </p>
*/
public Builder setSubText(CharSequence text) {
mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
@@ -2345,6 +2394,8 @@
* Set the large number at the right-hand side of the notification. This is
* equivalent to setContentInfo, although it might show the number in a different
* font size for readability.
+ *
+ * @deprecated this number is not shown anywhere anymore
*/
public Builder setNumber(int number) {
mN.number = number;
@@ -2356,6 +2407,10 @@
*
* The platform template will draw this on the last line of the notification, at the far
* right (to the right of a smallIcon if it has been placed there).
+ *
+ * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header.
+ * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this
+ * field will still show up, but the subtext will take precedence.
*/
public Builder setContentInfo(CharSequence info) {
mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
@@ -3009,10 +3064,8 @@
contentView.setBoolean(R.id.notification_header, "setExpanded", false);
contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
- contentView.setViewVisibility(R.id.header_sub_text, View.GONE);
- contentView.setViewVisibility(R.id.header_content_info, View.GONE);
- contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
- contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
+ contentView.setViewVisibility(R.id.header_text, View.GONE);
+ contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
contentView.setViewVisibility(R.id.time_divider, View.GONE);
contentView.setImageViewIcon(R.id.profile_badge, null);
contentView.setViewVisibility(R.id.profile_badge, View.GONE);
@@ -3112,39 +3165,12 @@
private void bindNotificationHeader(RemoteViews contentView) {
bindSmallIcon(contentView);
bindHeaderAppName(contentView);
- bindHeaderSubText(contentView);
- bindContentInfo(contentView);
+ bindHeaderText(contentView);
bindHeaderChronometerAndTime(contentView);
bindExpandButton(contentView);
bindProfileBadge(contentView);
}
- private void bindContentInfo(RemoteViews contentView) {
- boolean visible = false;
- if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
- contentView.setTextViewText(R.id.header_content_info,
- processLegacyText(mN.extras.getCharSequence(EXTRA_INFO_TEXT)));
- contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
- visible = true;
- } else if (mN.number > 0) {
- final int tooBig = mContext.getResources().getInteger(
- R.integer.status_bar_notification_info_maxnum);
- if (mN.number > tooBig) {
- contentView.setTextViewText(R.id.header_content_info, processLegacyText(
- mContext.getResources().getString(
- R.string.status_bar_notification_info_overflow)));
- } else {
- contentView.setTextViewText(R.id.header_content_info,
- processLegacyText(String.valueOf(mN.number)));
- }
- contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
- visible = true;
- }
- if (visible) {
- contentView.setViewVisibility(R.id.content_info_divider, View.VISIBLE);
- }
- }
-
private void bindExpandButton(RemoteViews contentView) {
contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(),
PorterDuff.Mode.SRC_ATOP, -1);
@@ -3169,17 +3195,22 @@
}
}
- private void bindHeaderSubText(RemoteViews contentView) {
- CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
- if (subText == null && mStyle != null && mStyle.mSummaryTextSet
+ private void bindHeaderText(RemoteViews contentView) {
+ CharSequence headerText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
&& mStyle.hasSummaryInHeader()) {
- subText = mStyle.mSummaryText;
+ headerText = mStyle.mSummaryText;
}
- if (subText != null) {
+ if (headerText == null
+ && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
+ && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
+ headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
+ }
+ if (headerText != null) {
// TODO: Remove the span entirely to only have the string with propper formating.
- contentView.setTextViewText(R.id.header_sub_text, processLegacyText(subText));
- contentView.setViewVisibility(R.id.header_sub_text, View.VISIBLE);
- contentView.setViewVisibility(R.id.sub_text_divider, View.VISIBLE);
+ contentView.setTextViewText(R.id.header_text, processLegacyText(headerText));
+ contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
+ contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
}
}
@@ -4863,6 +4894,7 @@
private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
+ private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -5429,6 +5461,29 @@
return mHintScreenTimeout;
}
+ /**
+ * Set a hint that this notification's content intent will launch an {@link Activity}
+ * directly, telling the platform that it can generate the appropriate transitions.
+ * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
+ * an activity and transitions should be generated, false otherwise.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintContentIntentLaunchesActivity(
+ boolean hintContentIntentLaunchesActivity) {
+ setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's content intent will launch an {@link Activity}
+ * directly, telling the platform that it can generate the appropriate transitions
+ * @return {@code true} if the content intent will launch an activity and transitions should
+ * be generated, false otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintContentIntentLaunchesActivity() {
+ return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f15b8fe..96757bb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2288,6 +2288,23 @@
}
/**
+ * Returns maximum time to lock that applied by all profiles in this user. We do this because we
+ * do not have a separate timeout to lock for work challenge only.
+ *
+ * @hide
+ */
+ public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getMaximumTimeToLockForUserAndProfiles(userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return 0;
+ }
+
+ /**
* Make the device lock immediately, as if the lock screen timeout has expired at the point of
* this call.
* <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1fb2283..6df1038 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -80,6 +80,7 @@
void setMaximumTimeToLock(in ComponentName who, long timeMs, boolean parent);
long getMaximumTimeToLock(in ComponentName who, int userHandle, boolean parent);
+ long getMaximumTimeToLockForUserAndProfiles(int userHandle);
void lockNow(boolean parent);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index cd67b3e..4db4567 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
package android.content;
import android.accounts.Account;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -60,6 +61,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -289,6 +292,31 @@
/** @hide */
public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ NOTIFY_SYNC_TO_NETWORK,
+ NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NotifyFlags {}
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: attempt to sync the change
+ * to the network.
+ */
+ public static final int NOTIFY_SYNC_TO_NETWORK = 1<<0;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: if set, this notification
+ * will be skipped if it is being delivered to the root URI of a ContentObserver that is
+ * using "notify for descendants." The purpose of this is to allow the provide to send
+ * a general notification of "something under X" changed that observers of that specific
+ * URI can receive, while also sending a specific URI under X. It would use this flag
+ * when sending the former, so that observers of "X and descendants" only see the latter.
+ */
+ public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
+
// Always log queries which take 500ms+; shorter queries are
// sampled accordingly.
private static final boolean ENABLE_CONTENT_SAMPLE = false;
@@ -1676,7 +1704,7 @@
* The observer that originated the change will only receive the notification if it
* has requested to receive self-change notifications by implementing
* {@link ContentObserver#deliverSelfNotifications()} to return true.
- * @param syncToNetwork If true, attempt to sync the change to the network.
+ * @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}.
* @see #requestSync(android.accounts.Account, String, android.os.Bundle)
*/
public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
@@ -1690,6 +1718,32 @@
}
/**
+ * Notify registered observers that a row was updated.
+ * To register, call {@link #registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver) registerContentObserver()}.
+ * By default, CursorAdapter objects will get this notification.
+ * If syncToNetwork is true, this will attempt to schedule a local sync using the sync
+ * adapter that's registered for the authority of the provided uri. No account will be
+ * passed to the sync adapter, so all matching accounts will be synchronized.
+ *
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be <code>null</null>.
+ * The observer that originated the change will only receive the notification if it
+ * has requested to receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return true.
+ * @param flags Additional flags: {@link #NOTIFY_SYNC_TO_NETWORK}.
+ * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
+ */
+ public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+ @NotifyFlags int flags) {
+ Preconditions.checkNotNull(uri, "uri");
+ notifyChange(
+ ContentProvider.getUriWithoutUserId(uri),
+ observer,
+ flags,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
+ }
+
+ /**
* Notify registered observers within the designated user(s) that a row was updated.
*
* @hide
@@ -1699,7 +1753,24 @@
try {
getContentService().notifyChange(
uri, observer == null ? null : observer.getContentObserver(),
- observer != null && observer.deliverSelfNotifications(), syncToNetwork,
+ observer != null && observer.deliverSelfNotifications(),
+ syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
+ userHandle);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @hide
+ */
+ public void notifyChange(Uri uri, ContentObserver observer, @NotifyFlags int flags,
+ @UserIdInt int userHandle) {
+ try {
+ getContentService().notifyChange(
+ uri, observer == null ? null : observer.getContentObserver(),
+ observer != null && observer.deliverSelfNotifications(), flags,
userHandle);
} catch (RemoteException e) {
}
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index d47e780..3446e03 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -52,7 +52,7 @@
* USER_CURRENT are properly interpreted.
*/
void notifyChange(in Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork,
+ boolean observerWantsSelfNotifications, int flags,
int userHandle);
void requestSync(in Account account, String authority, in Bundle extras);
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ed5dfa5..22ab43b 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -449,11 +449,12 @@
}
/**
- * Modify priority of this filter. The default priority is 0. Positive
- * values will be before the default, lower values will be after it.
- * Applications must use a value that is larger than
- * {@link #SYSTEM_LOW_PRIORITY} and smaller than
- * {@link #SYSTEM_HIGH_PRIORITY} .
+ * Modify priority of this filter. This only affects receiver filters.
+ * The priority of activity filters are set in XML and cannot be changed
+ * programatically. The default priority is 0. Positive values will be
+ * before the default, lower values will be after it. Applications should
+ * use a value that is larger than {@link #SYSTEM_LOW_PRIORITY} and
+ * smaller than {@link #SYSTEM_HIGH_PRIORITY} .
*
* @param priority The new priority value.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fe8db9f..aa1e372 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -385,6 +385,7 @@
public final int installLocation;
public final VerifierInfo[] verifiers;
public final Signature[] signatures;
+ public final Certificate[][] certificates;
public final boolean coreApp;
public final boolean multiArch;
public final boolean use32bitAbi;
@@ -392,8 +393,8 @@
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, boolean coreApp, boolean multiArch, boolean use32bitAbi,
- boolean extractNativeLibs) {
+ Signature[] signatures, Certificate[][] certificates, boolean coreApp,
+ boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
@@ -402,6 +403,7 @@
this.installLocation = installLocation;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
this.signatures = signatures;
+ this.certificates = certificates;
this.coreApp = coreApp;
this.multiArch = multiArch;
this.use32bitAbi = use32bitAbi;
@@ -1074,6 +1076,43 @@
}
/**
+ * Populates the correct packages fields with the given certificates.
+ * <p>
+ * This is useful when we've already processed the certificates [such as during package
+ * installation through an installer session]. We don't re-process the archive and
+ * simply populate the correct fields.
+ */
+ public static void populateCertificates(Package pkg, Certificate[][] certificates)
+ throws PackageParserException {
+ pkg.mCertificates = null;
+ pkg.mSignatures = null;
+ pkg.mSigningKeys = null;
+
+ pkg.mCertificates = certificates;
+ try {
+ pkg.mSignatures = convertToSignatures(certificates);
+ } catch (CertificateEncodingException e) {
+ // certificates weren't encoded properly; something went wrong
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed to collect certificates from " + pkg.baseCodePath, e);
+ }
+ pkg.mSigningKeys = new ArraySet<>(certificates.length);
+ for (int i = 0; i < certificates.length; i++) {
+ Certificate[] signerCerts = certificates[i];
+ Certificate signerCert = signerCerts[0];
+ pkg.mSigningKeys.add(signerCert.getPublicKey());
+ }
+ // add signatures to child packages
+ final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ Package childPkg = pkg.childPackages.get(i);
+ childPkg.mCertificates = pkg.mCertificates;
+ childPkg.mSignatures = pkg.mSignatures;
+ childPkg.mSigningKeys = pkg.mSigningKeys;
+ }
+ }
+
+ /**
* Collect certificates from all the APKs described in the given package,
* populating {@link Package#mSignatures}. Also asserts that all APK
* contents are signed correctly and consistently.
@@ -1304,6 +1343,7 @@
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Signature[] signatures;
+ final Certificate[][] certificates;
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
// TODO: factor signature related items out of Package object
final Package tempPkg = new Package(null);
@@ -1314,12 +1354,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
signatures = tempPkg.mSignatures;
+ certificates = tempPkg.mCertificates;
} else {
signatures = null;
+ certificates = null;
}
final AttributeSet attrs = parser;
- return parseApkLite(apkPath, res, parser, attrs, flags, signatures);
+ return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);
} catch (XmlPullParserException | IOException | RuntimeException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
@@ -1405,8 +1447,8 @@
}
private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, Signature[] signatures) throws IOException,
- XmlPullParserException, PackageParserException {
+ AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates)
+ throws IOException, XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -1466,8 +1508,8 @@
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- revisionCode, installLocation, verifiers, signatures, coreApp, multiArch,
- use32bitAbi, extractNativeLibs);
+ revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
+ multiArch, use32bitAbi, extractNativeLibs);
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index a762f59..34a9523 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -26,6 +26,7 @@
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.LruCache;
@@ -1311,7 +1312,8 @@
operation.mBindArgs.clear();
}
}
- operation.mStartTime = System.currentTimeMillis();
+ operation.mStartWallTime = System.currentTimeMillis();
+ operation.mStartTime = SystemClock.uptimeMillis();
operation.mKind = kind;
operation.mSql = sql;
if (bindArgs != null) {
@@ -1376,7 +1378,7 @@
Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- operation.mEndTime = System.currentTimeMillis();
+ operation.mEndTime = SystemClock.uptimeMillis();
operation.mFinished = true;
return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
operation.mEndTime - operation.mStartTime);
@@ -1454,8 +1456,9 @@
// marker for us, potentially losing metadata in the process).
private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
- public long mStartTime;
- public long mEndTime;
+ public long mStartWallTime; // in System.currentTimeMillis()
+ public long mStartTime; // in SystemClock.uptimeMillis();
+ public long mEndTime; // in SystemClock.uptimeMillis();
public String mKind;
public String mSql;
public ArrayList<Object> mBindArgs;
@@ -1468,7 +1471,7 @@
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
} else {
- msg.append(" started ").append(System.currentTimeMillis() - mStartTime)
+ msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
.append("ms ago");
}
msg.append(" - ").append(getStatus());
@@ -1519,7 +1522,7 @@
// relatively expensive to create during preloading. This method is only used
// when dumping a connection, which is a rare (mainly error) case. So:
// DO NOT CACHE.
- return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartTime));
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartWallTime));
}
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index b8088f3..4756b372 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -815,7 +815,8 @@
if (groupId != reqGroupId) {
Log.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId);
}
- mRemovalCallback.onRemovalSucceeded(mRemovalFingerprint);
+ mRemovalCallback.onRemovalSucceeded(new Fingerprint(null, groupId, fingerId,
+ deviceId));
}
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index fbac58c..47440de 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,9 +16,7 @@
package android.hardware.input;
-import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
-import com.android.internal.util.ArrayUtils;
import android.annotation.IntDef;
import android.annotation.SdkConstant;
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index 014e73f..10fc8e6 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -52,4 +52,11 @@
*/
public abstract void onInputMethodSubtypeChanged(int userId,
@Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype);
+
+ /**
+ * Toggles Caps Lock state for input device with specific id.
+ *
+ * @param deviceId The id of input device.
+ */
+ public abstract void toggleCapsLock(int deviceId);
}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index ae44f1d..194b9ee 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -345,6 +345,24 @@
mMemoryRegions = Arrays.copyOf(memoryRegions, memoryRegions.length);
}
+ @Override
+ public String toString() {
+ String retVal = "";
+ retVal += "Id : " + mId;
+ retVal += ", Name : " + mName;
+ retVal += "\n\tVendor : " + mVendor;
+ retVal += ", ToolChain : " + mToolchain;
+ retVal += "\n\tPlatformVersion : " + mPlatformVersion;
+ retVal += ", StaticSwVersion : " + mStaticSwVersion;
+ retVal += "\n\tPeakMips : " + mPeakMips;
+ retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
+ retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
+ retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
+ retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
+
+ return retVal;
+ }
+
private ContextHubInfo(Parcel in) {
mId = in.readInt();
mName = in.readString();
diff --git a/core/java/android/hardware/location/ContextHubService.java b/core/java/android/hardware/location/ContextHubService.java
index 3e6cb63..2b9b974 100644
--- a/core/java/android/hardware/location/ContextHubService.java
+++ b/core/java/android/hardware/location/ContextHubService.java
@@ -18,9 +18,12 @@
import android.Manifest;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -28,13 +31,12 @@
* @hide
*/
public class ContextHubService extends IContextHubService.Stub {
-
public static final String CONTEXTHUB_SERVICE = "contexthub_service";
private static final String TAG = "ContextHubService";
private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
- + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
+ + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
public static final int ANY_HUB = -1;
@@ -78,8 +80,8 @@
@Override
public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
- synchronized(this) {
- mCallback = callback;
+ synchronized (this) {
+ mCallback = callback;
}
return 0;
}
@@ -87,10 +89,10 @@
@Override
public int[] getContextHubHandles() throws RemoteException {
checkPermissions();
- int [] returnArray = new int[mContextHubInfo.length];
+ int[] returnArray = new int[mContextHubInfo.length];
for (int i = 0; i < returnArray.length; ++i) {
- returnArray[i] = i + 1; //valid handles from 1...n
+ returnArray[i] = i;
Log.d(TAG, String.format("Hub %s is mapped to %d",
mContextHubInfo[i].getName(), returnArray[i]));
}
@@ -101,7 +103,6 @@
@Override
public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
checkPermissions();
- contextHubHandle -= 1;
if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
return null; // null means fail
}
@@ -112,13 +113,12 @@
@Override
public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
checkPermissions();
- contextHubHandle -= 1;
if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
- return -1; // negative handle are invalid, means failed
+ Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+ return -1;
}
- // Call Native interface here
int[] msgHeader = new int[MSG_HEADER_SIZE];
msgHeader[MSG_FIELD_HUB_HANDLE] = contextHubHandle;
msgHeader[MSG_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
@@ -147,7 +147,7 @@
msgHeader[MSG_FIELD_VERSION] = 0;
msgHeader[MSG_FIELD_TYPE] = MSG_UNLOAD_NANO_APP;
- if(nativeSendMessage(msgHeader, null) != 0) {
+ if (nativeSendMessage(msgHeader, null) != 0) {
return -1;
}
@@ -157,7 +157,7 @@
@Override
public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
- throws RemoteException {
+ throws RemoteException {
checkPermissions();
// This assumes that all the nanoAppInfo is current. This is reasonable
// for the use cases for tightly controlled nanoApps.
@@ -173,10 +173,10 @@
checkPermissions();
ArrayList<Integer> foundInstances = new ArrayList<Integer>();
- for(Integer nanoAppInstance : mNanoAppHash.keySet()) {
+ for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
- if (filter.testMatch(info)){
+ if (filter.testMatch(info)) {
foundInstances.add(nanoAppInstance);
}
}
@@ -191,7 +191,7 @@
@Override
public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
- throws RemoteException {
+ throws RemoteException {
checkPermissions();
int[] msgHeader = new int[MSG_HEADER_SIZE];
@@ -203,6 +203,32 @@
return nativeSendMessage(msgHeader, msg.getData());
}
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump contexthub_service");
+ return;
+ }
+
+ pw.println("Dumping ContextHub Service");
+
+ pw.println("");
+ // dump ContextHubInfo
+ pw.println("=================== CONTEXT HUBS ====================");
+ for (int i = 0; i < mContextHubInfo.length; i++) {
+ pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+ }
+ pw.println("");
+ pw.println("=================== NANOAPPS ====================");
+ // Dump nanoAppHash
+ for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
+ }
+
+ // dump eventLog
+ }
+
private void checkPermissions() {
mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
}
@@ -212,16 +238,16 @@
return -1;
}
- synchronized(this) {
+ synchronized (this) {
if (mCallback != null) {
ContextHubMessage msg = new ContextHubMessage(header[MSG_FIELD_TYPE],
- header[MSG_FIELD_VERSION],
- data);
+ header[MSG_FIELD_VERSION],
+ data);
try {
mCallback.onMessageReceipt(header[MSG_FIELD_HUB_HANDLE],
- header[MSG_FIELD_APP_INSTANCE],
- msg);
+ header[MSG_FIELD_APP_INSTANCE],
+ msg);
} catch (Exception e) {
Log.w(TAG, "Exception " + e + " when calling remote callback");
return -1;
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index d100de2..857434e 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -79,6 +79,33 @@
}
@Override
+ public String toString() {
+ String mask = "";
+
+ if (isReadable()) {
+ mask += "r";
+ } else {
+ mask += "-";
+ }
+
+ if (isWritable()) {
+ mask += "w";
+ } else {
+ mask += "-";
+ }
+
+ if (isExecutable()) {
+ mask += "x";
+ } else {
+ mask += "-";
+ }
+
+ String retVal = "[ " + mSizeBytesFree + "/ " + mSizeBytes + " ] : " + mask;
+
+ return retVal;
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index b447b62..c8f3439 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -284,4 +284,14 @@
return new NanoApp[size];
}
};
+
+ @Override
+ public String toString() {
+ String retVal = "Id : " + mAppId;
+ retVal += ", Version : " + mAppVersion;
+ retVal += ", Name : " + mName;
+ retVal += ", Publisher : " + mPublisher;
+
+ return retVal;
+ }
}
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index 977f645..e842ec6 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -320,4 +320,15 @@
return new NanoAppInstanceInfo[size];
}
};
+
+ @Override
+ public String toString() {
+ String retVal = "handle : " + mHandle;
+ retVal += ", Id : 0x" + Long.toHexString(mAppId);
+ retVal += ", Version : " + mAppVersion;
+ retVal += ", Name : " + mName;
+ retVal += ", Publisher : " + mPublisher;
+
+ return retVal;
+ }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index b452341..a025337 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -767,6 +767,28 @@
}
/**
+ * Returns a {@link Network} object corresponding to the currently active
+ * default data network for a specific UID. In the event that the default data
+ * network disconnects, the returned {@code Network} object will no longer
+ * be usable. This will return {@code null} when there is no default
+ * network for the UID.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ *
+ * @return a {@link Network} object for the current default network for the
+ * given UID or {@code null} if no default network is currently active
+ *
+ * @hide
+ */
+ public Network getActiveNetworkForUid(int uid) {
+ try {
+ return mService.getActiveNetworkForUid(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
*
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 1a9c9ea..c897c45 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -44,6 +44,7 @@
interface IConnectivityManager
{
Network getActiveNetwork();
+ Network getActiveNetworkForUid(int uid);
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 710f884..ece1228 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1184,7 +1184,23 @@
/** {@hide} */
public static File maybeTranslateEmulatedPathToInternal(File path) {
- // Disabled now that FUSE has been replaced by sdcardfs
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ try {
+ final VolumeInfo[] vols = mountService.getVolumes(0);
+ for (VolumeInfo vol : vols) {
+ if ((vol.getType() == VolumeInfo.TYPE_EMULATED
+ || vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) {
+ final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(),
+ vol.getInternalPath(), path);
+ if (internalPath != null && internalPath.exists()) {
+ return internalPath;
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
return path;
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index afdd1d4..7325aef 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -69,6 +69,12 @@
* <action android:name="android.service.notification.NotificationListenerService" />
* </intent-filter>
* </service></pre>
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing any operations. The {@link #requestRebind(ComponentName)}
+ * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
+ * or after {@link #onListenerDisconnected()}.
+ * </p>
*/
public abstract class NotificationListenerService extends Service {
// TAG = "NotificationListenerService[MySubclass]"
@@ -164,6 +170,7 @@
/** @hide */
protected NotificationListenerWrapper mWrapper = null;
+ private boolean isConnected = false;
@GuardedBy("mLock")
private RankingMap mRankingMap;
@@ -222,10 +229,10 @@
/**
* Implement this method to learn when notifications are removed.
- * <P>
+ * <p>
* This might occur because the user has dismissed the notification using system UI (or another
* notification listener) or because the app has withdrawn the notification.
- * <P>
+ * <p>
* NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
* result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
* fields such as {@link android.app.Notification#contentView} and
@@ -243,10 +250,10 @@
/**
* Implement this method to learn when notifications are removed.
- * <P>
+ * <p>
* This might occur because the user has dismissed the notification using system UI (or another
* notification listener) or because the app has withdrawn the notification.
- * <P>
+ * <p>
* NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
* result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
* fields such as {@link android.app.Notification#contentView} and
@@ -275,6 +282,15 @@
}
/**
+ * Implement this method to learn about when the listener is disconnected from the
+ * notification manager.You will not receive any events after this call, and may only
+ * call {@link #requestRebind(ComponentName)} at this time.
+ */
+ public void onListenerDisconnected() {
+ // optional
+ }
+
+ /**
* Implement this method to be notified when the notification ranking changes.
*
* @param rankingMap The current ranking map that can be used to retrieve ranking information
@@ -322,12 +338,15 @@
* It should be called after the user dismisses a single notification using your UI;
* upon being informed, the notification manager will actually remove the notification
* and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
- * <P>
+ * <p>
* <b>Note:</b> If your listener allows the user to fire a notification's
* {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
* this method at that time <i>if</i> the Notification in question has the
* {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param pkg Package of the notifying app.
* @param tag Tag of the notification as specified by the notifying app in
* {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
@@ -357,12 +376,16 @@
* It should be called after the user dismisses a single notification using your UI;
* upon being informed, the notification manager will actually remove the notification
* and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
- * <P>
+ * <p>
* <b>Note:</b> If your listener allows the user to fire a notification's
* {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
* this method at that time <i>if</i> the Notification in question has the
* {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
* <p>
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
*/
public final void cancelNotification(String key) {
@@ -384,6 +407,9 @@
* upon being informed, the notification manager will actually remove all active notifications
* and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* {@see #cancelNotification(String, String, int)}
*/
public final void cancelAllNotifications() {
@@ -396,6 +422,9 @@
* Use this if your listener has a user interface that allows the user to dismiss
* multiple notifications at once.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param keys Notifications to dismiss, or {@code null} to dismiss all.
*
* {@see #cancelNotification(String, String, int)}
@@ -414,6 +443,10 @@
* user. This should only be called when there is sufficient confidence that the user is
* looking at the notifications, such as when the notifications appear on the screen due to
* an explicit user interaction.
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param keys Notifications to mark as seen.
*/
public final void setNotificationsShown(String[] keys) {
@@ -436,6 +469,9 @@
* <p>
* Set to {@link #TRIM_FULL} initially.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @hide
*
* @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
@@ -455,6 +491,9 @@
* Request the list of outstanding notifications (that is, those that are visible to the
* current user). Useful when you don't know what's already been posted.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @return An array of active notifications, sorted in natural order.
*/
public StatusBarNotification[] getActiveNotifications() {
@@ -480,6 +519,9 @@
* notifications but didn't want to retain the bits, and now need to go back and extract
* more data out of those notifications.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param keys the keys of the notifications to request
* @return An array of notifications corresponding to the requested keys, in the
* same order as the key list.
@@ -545,6 +587,9 @@
* shared across all listeners or a feature the notification host does not support or refuses
* to grant.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @return Zero or more of the HINT_ constants.
*/
public final int getCurrentListenerHints() {
@@ -572,6 +617,9 @@
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
* unavailable.
*/
@@ -594,6 +642,9 @@
* <p>
* Listen for updates using {@link #onListenerHintsChanged(int)}.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param hints One or more of the HINT_ constants.
*/
public final void requestListenerHints(int hints) {
@@ -614,6 +665,9 @@
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
*/
public final void requestInterruptionFilter(int interruptionFilter) {
@@ -640,6 +694,9 @@
* such events, for example to retrieve the RankingMap right after
* initialization.
*
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation.
+ *
* @return A {@link RankingMap} object providing access to ranking information
*/
public RankingMap getCurrentRanking() {
@@ -648,6 +705,12 @@
}
}
+ /**
+ * This is not the lifecycle event you are looking for.
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing any operations.
+ */
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
@@ -665,6 +728,12 @@
return true;
}
+ @Override
+ public void onDestroy() {
+ onListenerDisconnected();
+ super.onDestroy();
+ }
+
/**
* Directly register this service with the Notification Manager.
*
@@ -693,7 +762,7 @@
/**
* Directly unregister this service from the Notification Manager.
*
- * <P>This method will fail for listeners that were not registered
+ * <p>This method will fail for listeners that were not registered
* with (@link registerAsService).
* @hide
*/
@@ -708,11 +777,8 @@
/**
* Request that the listener be rebound, after a previous call to (@link requestUnbind).
*
- * <P>This method will fail for listeners that have
+ * <p>This method will fail for listeners that have
* not been granted the permission by the user.
- *
- * <P>The service should wait for the {@link #onListenerConnected()} event
- * before performing any operations.
*/
public static void requestRebind(ComponentName componentName)
throws RemoteException {
@@ -724,14 +790,19 @@
/**
* Request that the service be unbound.
*
- * <P>This will no longer receive updates until
+ * <p>This will no longer receive updates until
* {@link #requestRebind(ComponentName)} is called.
* The service will likely be kiled by the system after this call.
+ *
+ * <p>The service should wait for the {@link #onListenerConnected()} event
+ * before performing this operation. I know it's tempting, but you must wait.
*/
public final void requestUnbind() throws RemoteException {
if (mWrapper != null) {
INotificationManager noMan = getNotificationInterface();
noMan.requestUnbindListener(mWrapper);
+ // Disable future messages.
+ isConnected = false;
}
}
@@ -842,6 +913,7 @@
synchronized (mLock) {
applyUpdateLocked(update);
}
+ isConnected = true;
mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
}
@@ -1303,6 +1375,9 @@
@Override
public void handleMessage(Message msg) {
+ if (!isConnected) {
+ return;
+ }
switch (msg.what) {
case MSG_ON_NOTIFICATION_POSTED: {
SomeArgs args = (SomeArgs) msg.obj;
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
new file mode 100644
index 0000000..62ecab3
--- /dev/null
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.vr;
+
+import android.service.vr.IVrStateCallbacks;
+
+/** @hide */
+interface IVrManager {
+
+ /**
+ * Add a callback to be notified when VR mode state changes.
+ *
+ * @param cb the callback instance to add.
+ */
+ void registerListener(in IVrStateCallbacks cb);
+
+ /**
+ * Remove the callack from the current set of registered callbacks.
+ *
+ * @param cb the callback to remove.
+ */
+ void unregisterListener(in IVrStateCallbacks cb);
+
+ /**
+ * Return current VR mode state.
+ *
+ * @return {@code true} if VR mode is enabled.
+ */
+ boolean getVrModeState();
+
+}
+
diff --git a/core/java/android/service/vr/IVrStateCallbacks.aidl b/core/java/android/service/vr/IVrStateCallbacks.aidl
new file mode 100644
index 0000000..c4fdcd0
--- /dev/null
+++ b/core/java/android/service/vr/IVrStateCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.vr;
+
+/** @hide */
+oneway interface IVrStateCallbacks {
+
+ void onVrStateChanged(in boolean enabled);
+
+}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index e0c6770..636384c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1870,6 +1870,11 @@
return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
}
+ /** @hide */
+ public static final boolean isAltKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+ }
+
/** {@inheritDoc} */
@Override
public final int getDeviceId() {
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index cff9d8e..37da869 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -22,7 +22,6 @@
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RemoteViews;
-import android.widget.TextView;
import java.util.ArrayList;
@@ -37,7 +36,7 @@
private final int mChildMinWidth;
private final int mContentEndMargin;
private View mAppName;
- private View mSubTextView;
+ private View mHeaderText;
private OnClickListener mExpandClickListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private ImageView mExpandButton;
@@ -73,11 +72,10 @@
protected void onFinishInflate() {
super.onFinishInflate();
mAppName = findViewById(com.android.internal.R.id.app_name_text);
- mSubTextView = findViewById(com.android.internal.R.id.header_sub_text);
+ mHeaderText = findViewById(com.android.internal.R.id.header_text);
mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button);
mIcon = findViewById(com.android.internal.R.id.icon);
mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
- mInfo = findViewById(com.android.internal.R.id.header_content_info);
}
@Override
@@ -105,15 +103,7 @@
}
if (totalWidth > givenWidth) {
int overFlow = totalWidth - givenWidth;
- // We are overflowing, lets shrink the info first
- final int infoWidth = mInfo.getMeasuredWidth();
- if (mInfo.getVisibility() != GONE && infoWidth > mChildMinWidth) {
- int newSize = infoWidth - Math.min(infoWidth - mChildMinWidth, overFlow);
- int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- mInfo.measure(childWidthSpec, wrapContentHeightSpec);
- overFlow -= infoWidth - newSize;
- }
- // still overflowing, lets shrink the app name now
+ // We are overflowing, lets shrink the app name first
final int appWidth = mAppName.getMeasuredWidth();
if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) {
int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow);
@@ -121,13 +111,13 @@
mAppName.measure(childWidthSpec, wrapContentHeightSpec);
overFlow -= appWidth - newSize;
}
- // still overflowing, finaly we shrink the subtext
- if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
+ // still overflowing, finaly we shrink the header text
+ if (overFlow > 0 && mHeaderText.getVisibility() != GONE) {
// we're still too big
- final int subTextWidth = mSubTextView.getMeasuredWidth();
- int newSize = Math.max(0, subTextWidth - overFlow);
+ final int textWidth = mHeaderText.getMeasuredWidth();
+ int newSize = Math.max(0, textWidth - overFlow);
int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
+ mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
}
}
setMeasuredDimension(givenWidth, givenHeight);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 23b0df2..96f179b 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1397,4 +1397,9 @@
* is allowed, so for example, if DOCKED_RIGHT is not allowed, DOCKED_LEFT is allowed.
*/
public boolean isDockSideAllowed(int dockSide);
+
+ /**
+ * Called when the configuration has changed, and it's safe to load new values from resources.
+ */
+ public void onConfigurationChanged();
}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 1536c29..d89c172 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -93,8 +93,12 @@
*/
public static final int ZORDER_BOTTOM = -1;
- private static final boolean USE_CLOSEGUARD
- = SystemProperties.getBoolean("log.closeguard.Animation", false);
+ // Use a preload holder to isolate static initialization into inner class, which allows
+ // Animation and its subclasses to be compile-time initialized.
+ private static class NoImagePreloadHolder {
+ public static final boolean USE_CLOSEGUARD
+ = SystemProperties.getBoolean("log.closeguard.Animation", false);
+ }
/**
* Set by {@link #getTransformation(long, Transformation)} when the animation ends.
@@ -859,7 +863,7 @@
if (!mStarted) {
fireAnimationStart();
mStarted = true;
- if (USE_CLOSEGUARD) {
+ if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index e3357a7..0c5edc5 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -339,7 +339,9 @@
*
* @return {@code true} if the calendar view is shown
* @see #getCalendarView()
+ * @deprecated Not supported by Material-style {@code calendar} mode
*/
+ @Deprecated
public boolean getCalendarViewShown() {
return mDelegate.getCalendarViewShown();
}
@@ -347,13 +349,18 @@
/**
* Returns the {@link CalendarView} used by this picker.
* <p>
- * <strong>Note:</strong> This method returns {@code null} when the
+ * <strong>Note:</strong> This method throws an
+ * {@link UnsupportedOperationException} when the
* {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
* to {@code calendar}.
*
* @return the calendar view
* @see #getCalendarViewShown()
+ * @deprecated Not supported by Material-style {@code calendar} mode
+ * @throws UnsupportedOperationException if called when the picker is
+ * displayed in {@code calendar} mode
*/
+ @Deprecated
public CalendarView getCalendarView() {
return mDelegate.getCalendarView();
}
@@ -367,7 +374,9 @@
*
* @param shown {@code true} to show the calendar view, {@code false} to
* hide it
+ * @deprecated Not supported by Material-style {@code calendar} mode
*/
+ @Deprecated
public void setCalendarViewShown(boolean shown) {
mDelegate.setCalendarViewShown(shown);
}
@@ -380,7 +389,9 @@
* to {@code calendar}.
*
* @return {@code true} if the spinners are shown
+ * @deprecated Not supported by Material-style {@code calendar} mode
*/
+ @Deprecated
public boolean getSpinnersShown() {
return mDelegate.getSpinnersShown();
}
@@ -394,7 +405,9 @@
*
* @param shown {@code true} to show the spinners, {@code false} to hide
* them
+ * @deprecated Not supported by Material-style {@code calendar} mode
*/
+ @Deprecated
public void setSpinnersShown(boolean shown) {
mDelegate.setSpinnersShown(shown);
}
diff --git a/core/java/android/widget/DayPickerPagerAdapter.java b/core/java/android/widget/DayPickerPagerAdapter.java
index 8ce2f9c..97936e7 100644
--- a/core/java/android/widget/DayPickerPagerAdapter.java
+++ b/core/java/android/widget/DayPickerPagerAdapter.java
@@ -148,18 +148,22 @@
void setCalendarTextColor(ColorStateList calendarTextColor) {
mCalendarTextColor = calendarTextColor;
+ notifyDataSetChanged();
}
void setDaySelectorColor(ColorStateList selectorColor) {
mDaySelectorColor = selectorColor;
+ notifyDataSetChanged();
}
void setMonthTextAppearance(int resId) {
mMonthTextAppearance = resId;
+ notifyDataSetChanged();
}
void setDayOfWeekTextAppearance(int resId) {
mDayOfWeekTextAppearance = resId;
+ notifyDataSetChanged();
}
int getDayOfWeekTextAppearance() {
@@ -168,6 +172,7 @@
void setDayTextAppearance(int resId) {
mDayTextAppearance = resId;
+ notifyDataSetChanged();
}
int getDayTextAppearance() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7055f78..440ef21 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1778,6 +1778,18 @@
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
+ void invalidateHandlesAndActionMode() {
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.invalidateHandles();
+ }
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.invalidateHandle();
+ }
+ if (mTextActionMode != null) {
+ mTextActionMode.invalidate();
+ }
+ }
+
/**
* Invalidates all the sub-display lists that overlap the specified character range
*/
@@ -3462,7 +3474,6 @@
popupBackground.getPadding(mTempRect);
width += mTempRect.left + mTempRect.right;
}
- mSuggestionListView.getLayoutParams().width = width;
mPopupWindow.setWidth(width);
}
@@ -4104,6 +4115,14 @@
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
}
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ if (isShowing()) {
+ positionAtCursorOffset(getCurrentCursorOffset(), true);
+ }
+ };
+
private int getPreferredWidth() {
return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
}
@@ -4170,7 +4189,12 @@
return mTextView.getOffsetAtCoordinate(line, x);
}
- protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
+ /**
+ * @param offset Cursor offset. Must be in [-1, length].
+ * @param forceUpdatePosition whether to force update the position. This should be true
+ * when If the parent has been scrolled, for example.
+ */
+ protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition) {
// A HandleView relies on the layout, which may be nulled by external methods
Layout layout = mTextView.getLayout();
if (layout == null) {
@@ -4181,7 +4205,7 @@
layout = mTextView.getLayout();
boolean offsetChanged = offset != mPreviousOffset;
- if (offsetChanged || parentScrolled) {
+ if (offsetChanged || forceUpdatePosition) {
if (offsetChanged) {
updateSelection(offset);
addPositionToTouchUpFilter(offset);
@@ -4782,13 +4806,9 @@
mPrevX = x;
}
- /**
- * @param offset Cursor offset. Must be in [-1, length].
- * @param parentScrolled If the parent has been scrolled or not.
- */
@Override
- protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
- super.positionAtCursorOffset(offset, parentScrolled);
+ protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition) {
+ super.positionAtCursorOffset(offset, forceUpdatePosition);
mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
}
@@ -5014,6 +5034,12 @@
public boolean isActive() {
return mHandle != null && mHandle.isShowing();
}
+
+ public void invalidateHandle() {
+ if (mHandle != null) {
+ mHandle.invalidate();
+ }
+ }
}
class SelectionModifierCursorController implements CursorController {
@@ -5418,6 +5444,15 @@
public boolean isActive() {
return mStartHandle != null && mStartHandle.isShowing();
}
+
+ public void invalidateHandles() {
+ if (mStartHandle != null) {
+ mStartHandle.invalidate();
+ }
+ if (mEndHandle != null) {
+ mEndHandle.invalidate();
+ }
+ }
}
private class CorrectionHighlighter {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 4483b7ba..48fd58b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3350,7 +3350,10 @@
mShadowColor = color;
// Will change text clip region
- if (mEditor != null) mEditor.invalidateTextDisplayList();
+ if (mEditor != null) {
+ mEditor.invalidateTextDisplayList();
+ mEditor.invalidateHandlesAndActionMode();
+ }
invalidate();
}
@@ -8306,6 +8309,7 @@
if (mEditor != null) {
if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
+ mEditor.invalidateHandlesAndActionMode();
}
}
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index e330de2..171a264 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -50,9 +50,12 @@
* Called when the device has finished going to sleep.
*
* @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
- * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ * @param cameraGestureTriggered whether the camera gesture was triggered between
+ * {@link #onStartedGoingToSleep} and this method; if it's been
+ * triggered, we shouldn't lock the device.
*/
- void onFinishedGoingToSleep(int reason);
+ void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered);
/**
* Called when the device has started waking up.
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index 29a9c8e..ddca51f 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -346,7 +346,15 @@
private void showMenu(@NonNull MenuBuilder menu) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly);
- adapter.setForceShowIcon(mForceShowIcon);
+
+ // Apply "force show icon" setting; if the menu being shown is the top level menu, apply the
+ // setting set on this CascadingMenuPopup by its creating code. If it's a submenu, or if no
+ // "force" setting was explicitly set, determine the setting by examining the items.
+ if (!isShowing() && mForceShowIcon) {
+ adapter.setForceShowIcon(mForceShowIcon);
+ } else {
+ adapter.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(menu));
+ }
final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
final MenuPopupWindow popupWindow = createPopupWindow();
diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java
index 42b1a56..16e4156 100644
--- a/core/java/com/android/internal/view/menu/MenuPopup.java
+++ b/core/java/com/android/internal/view/menu/MenuPopup.java
@@ -183,4 +183,25 @@
}
return (MenuAdapter) adapter;
}
+
+ /**
+ * Returns whether icon spacing needs to be preserved for the given menu, based on whether any
+ * of its items contains an icon.
+ * @param menu
+ * @return Whether to preserve icon spacing.
+ */
+ protected static boolean shouldPreserveIconSpacing(MenuBuilder menu) {
+ boolean preserveIconSpacing = false;
+ final int count = menu.size();
+
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = menu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+
+ return preserveIconSpacing;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index 8ced36f..d8ed92d 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -266,7 +266,7 @@
final MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu,
mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
subPopup.setPresenterCallback(mPresenterCallback);
- subPopup.setForceShowIcon(mAdapter.getForceShowIcon());
+ subPopup.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(subMenu));
// Pass responsibility for handling onDismiss to the submenu.
subPopup.setOnDismissListener(mOnDismissListener);
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b07e36a..21c4d12 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -33,9 +33,12 @@
void setLockPassword(in String password, in String savedPassword, int userId);
VerifyCredentialResponse checkPassword(in String password, int userId);
VerifyCredentialResponse verifyPassword(in String password, long challenge, int userId);
+ VerifyCredentialResponse verifyTiedProfileChallenge(String password, boolean isPattern, long challenge, int userId);
boolean checkVoldPassword(int userId);
boolean havePattern(int userId);
boolean havePassword(int userId);
+ void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword);
+ boolean getSeparateProfileChallengeEnabled(int userId);
void registerStrongAuthTracker(in IStrongAuthTracker tracker);
void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
void requireStrongAuth(int strongAuthReason, int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 4880664..713f56f 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -145,6 +145,43 @@
}
/**
+ * Verify a password asynchronously.
+ *
+ * @param utils The LockPatternUtils instance to use.
+ * @param password The password to check.
+ * @param challenge The challenge to verify against the pattern.
+ * @param userId The user to check against the pattern.
+ * @param callback The callback to be invoked with the verification result.
+ */
+ public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
+ final String password,
+ final boolean isPattern,
+ final long challenge,
+ final int userId,
+ final OnVerifyCallback callback) {
+ AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
+ private int mThrottleTimeout;
+
+ @Override
+ protected byte[] doInBackground(Void... args) {
+ try {
+ return utils.verifyTiedProfileChallenge(password, isPattern, challenge, userId);
+ } catch (RequestThrottledException ex) {
+ mThrottleTimeout = ex.getTimeoutMs();
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(byte[] result) {
+ callback.onVerified(result, mThrottleTimeout);
+ }
+ };
+ task.execute();
+ return task;
+ }
+
+ /**
* Checks a password asynchronously.
*
* @param utils The LockPatternUtils instance to use.
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3d892af..bceeaca 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -137,8 +137,6 @@
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
- private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
-
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
@@ -377,6 +375,36 @@
}
}
+
+ /**
+ * Check to see if a password matches the saved password.
+ * If password matches, return an opaque attestation that the challenge
+ * was verified.
+ *
+ * @param password The password to check.
+ * @param challenge The challenge to verify against the password
+ * @return the attestation that the challenge was verified, or null.
+ */
+ public byte[] verifyTiedProfileChallenge(String password, boolean isPattern, long challenge,
+ int userId) throws RequestThrottledException {
+ throwIfCalledOnMainThread();
+ try {
+ VerifyCredentialResponse response =
+ getLockSettings().verifyTiedProfileChallenge(password, isPattern, challenge,
+ userId);
+
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ return response.getPayload();
+ } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ throw new RequestThrottledException(response.getTimeout());
+ } else {
+ return null;
+ }
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
/**
* Check to see if a password matches the saved password. If no password exists,
* always returns true.
@@ -785,6 +813,7 @@
}
getLockSettings().setLockPassword(password, savedPassword, userHandle);
+ getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
int computedQuality = computePasswordQuality(password);
// Update the device encryption password.
@@ -919,11 +948,23 @@
/**
* Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
* for user handles that do not belong to a managed profile.
+ *
+ * @param userHandle Managed profile user id
+ * @param enabled True if separate challenge is enabled
+ * @param managedUserPassword Managed profile previous password. Null when {@param enabled} is
+ * true
*/
- public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled) {
+ public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
+ String managedUserPassword) {
UserInfo info = getUserManager().getUserInfo(userHandle);
if (info.isManagedProfile()) {
- setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userHandle);
+ try {
+ getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
+ managedUserPassword);
+ onAfterChangingPassword(userHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't update work profile challenge enabled");
+ }
}
}
@@ -935,7 +976,13 @@
if (info == null || !info.isManagedProfile()) {
return false;
}
- return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userHandle);
+ try {
+ return getLockSettings().getSeparateProfileChallengeEnabled(userHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't get separate profile challenge enabled");
+ // Default value is false
+ return false;
+ }
}
/**
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index c4347f8..25b487e 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -17,9 +17,14 @@
package com.android.internal.widget;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -85,6 +90,10 @@
private final float mMinFlingVelocity;
private final OverScroller mScroller;
private final VelocityTracker mVelocityTracker;
+ private final Drawable mScrollIndicatorDrawable;
+ private final Drawable mFakeForeground;
+
+ private View mButtonBar;
private OnDismissedListener mOnDismissedListener;
private RunOnDismissedListener mRunOnDismissedListener;
@@ -106,6 +115,8 @@
}
};
+ private final int[] mTempOffset = new int[2];
+
public ResolverDrawerLayout(Context context) {
this(context, null);
}
@@ -127,6 +138,9 @@
mMaxCollapsedHeight);
a.recycle();
+ mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
+ mFakeForeground = new ColorDrawable(Color.TRANSPARENT);
+
mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context,
android.R.interpolator.decelerate_quint));
mVelocityTracker = VelocityTracker.obtain();
@@ -138,6 +152,13 @@
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mButtonBar = findViewById(R.id.button_bar);
+ }
+
public void setSmallCollapsed(boolean smallCollapsed) {
mSmallCollapsed = smallCollapsed;
requestLayout();
@@ -202,8 +223,7 @@
}
final boolean isCollapsedNew = mCollapseOffset != 0;
if (isCollapsedOld != isCollapsedNew) {
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ onCollapsedChanged(isCollapsedNew);
}
} else {
// Start out collapsed at first unless we restored state for otherwise
@@ -442,8 +462,7 @@
mTopOffset += dy;
final boolean isCollapsedNew = newPos != 0;
if (isCollapsedOld != isCollapsedNew) {
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ onCollapsedChanged(isCollapsedNew);
}
postInvalidateOnAnimation();
return dy;
@@ -451,6 +470,14 @@
return 0;
}
+ private void onCollapsedChanged(boolean isCollapsed) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+
+ // Set a fake foreground so that we receive onDrawForeground().
+ setForeground(isCollapsed ? mFakeForeground : null);
+ }
+
void dispatchOnDismissed() {
if (mOnDismissedListener != null) {
mOnDismissedListener.onDismissed();
@@ -709,6 +736,23 @@
}
@Override
+ public void onDrawForeground(Canvas canvas) {
+ if (isCollapsed() && mButtonBar != null) {
+ // Draw the scroll indicator directly above the button bar.
+ final int height = mScrollIndicatorDrawable.getIntrinsicHeight();
+ mButtonBar.getLocationInWindow(mTempOffset);
+ final int barTop = mTempOffset[1];
+ getLocationInWindow(mTempOffset);
+ final int myTop = mTempOffset[1];
+ final int top = (barTop - myTop) - height;
+ mScrollIndicatorDrawable.setBounds(0, top, getWidth(), top + height);
+ mScrollIndicatorDrawable.draw(canvas);
+ }
+
+ super.onDrawForeground(canvas);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
int widthSize = sourceWidth;
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 1844a98..f46f45c 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -768,6 +768,22 @@
return false;
}
+ // Returns true if the given string is exact one pair of regional indicators.
+ static bool isFlag(const jchar* str, size_t length) {
+ const jchar RI_LEAD_SURROGATE = 0xD83C;
+ const jchar RI_TRAIL_SURROGATE_MIN = 0xDDE6;
+ const jchar RI_TRAIL_SURROGATE_MAX = 0xDDFF;
+
+ if (length != 4) {
+ return false;
+ }
+ if (str[0] != RI_LEAD_SURROGATE || str[2] != RI_LEAD_SURROGATE) {
+ return false;
+ }
+ return RI_TRAIL_SURROGATE_MIN <= str[1] && str[1] <= RI_TRAIL_SURROGATE_MAX &&
+ RI_TRAIL_SURROGATE_MIN <= str[3] && str[3] <= RI_TRAIL_SURROGATE_MAX;
+ }
+
static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
jint bidiFlags, jstring string) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
@@ -817,7 +833,26 @@
// in joining scripts, such as Arabic and Mongolian.
return false;
}
- return nGlyphs > 0 && !layoutContainsNotdef(layout);
+
+ if (nGlyphs == 0 || layoutContainsNotdef(layout)) {
+ return false; // The collection doesn't have a glyph.
+ }
+
+ if (nChars == 2 && isFlag(str.get(), str.size())) {
+ // Some font may have a special glyph for unsupported regional indicator pairs.
+ // To return false for this case, need to compare the glyph id with the one of ZZ
+ // since ZZ is reserved for unknown or invalid territory.
+ // U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16.
+ static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF };
+ Layout zzLayout;
+ MinikinUtils::doLayout(&zzLayout, paint, bidiFlags, typeface, ZZ_FLAG_STR, 0, 4, 4);
+ if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) {
+ // The font collection doesn't have a glyph for unknown flag. Just return true.
+ return true;
+ }
+ return zzLayout.getGlyphId(0) != layout.getGlyphId(0);
+ }
+ return true;
}
static jfloat doRunAdvance(const Paint* paint, Typeface* typeface, const jchar buf[],
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 80ae550..91a3b4f 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -382,6 +382,9 @@
char *msg, int msgLen) {
int retVal;
+ //ALOGD("Rcd OS message from hubHandle %" PRIu32 " type %" PRIu32 " length %d",
+ // hubHandle, msgType, msgLen);
+
switch(msgType) {
case CONTEXT_HUB_APPS_ENABLE:
retVal = 0;
@@ -633,7 +636,7 @@
if (numHeaderElements >= MSG_HEADER_SIZE) {
- int setAddressSuccess;
+ bool setAddressSuccess;
int hubId;
hub_message_t msg;
@@ -654,7 +657,7 @@
ALOGD("Could not find app instance %d on hubHandle %d, setAddress %d",
header[HEADER_FIELD_APP_INSTANCE],
header[HEADER_FIELD_HUB_HANDLE],
- setAddressSuccess);
+ (int)setAddressSuccess);
}
} else {
ALOGD("Malformed header len");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 88d75dd..9a2e39c7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2997,6 +2997,11 @@
<permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Required to make calls to {@link android.service.vr.IVrManager}.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VR_MANAGER"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to whitelist tasks during lock task mode
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
diff --git a/core/res/res/anim/slide_in_enter_micro.xml b/core/res/res/anim/slide_in_enter_micro.xml
index 14a5290..c70874c 100644
--- a/core/res/res/anim/slide_in_enter_micro.xml
+++ b/core/res/res/anim/slide_in_enter_micro.xml
@@ -19,7 +19,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
- <translate android:fromYDelta="5%p" android:toXDelta="0"
+ <translate android:fromYDelta="5%p" android:toYDelta="0"
android:duration="417"
android:interpolator="@android:interpolator/launch_task_micro_ydelta" />
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 0bbaa24..992e88e 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -42,7 +42,7 @@
android:singleLine="true"
/>
<TextView
- android:id="@+id/sub_text_divider"
+ android:id="@+id/header_text_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.Notification.Info"
@@ -51,26 +51,7 @@
android:text="@string/notification_header_divider_symbol"
android:visibility="gone"/>
<TextView
- android:id="@+id/header_sub_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:visibility="gone"
- android:singleLine="true"/>
- <TextView
- android:id="@+id/content_info_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:text="@string/notification_header_divider_symbol"
- android:singleLine="true"
- android:visibility="gone"/>
- <TextView
- android:id="@+id/header_content_info"
+ android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.Notification.Info"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 4b8640c..fe43e1c 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -30,33 +30,37 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:elevation="8dp"
- android:background="@color/white" >
- <TextView android:id="@+id/profile_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:visibility="gone"
- style="?attr/borderlessButtonStyle"
- android:textAppearance="?attr/textAppearanceButton"
- android:textColor="@color/material_deep_teal_500"
- android:gravity="center_vertical"
- android:layout_alignParentTop="true"
- android:layout_alignParentRight="true"
- android:singleLine="true"/>
- <TextView android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minHeight="56dp"
- android:textAppearance="?attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="8dp"
- android:layout_below="@id/profile_button"
- android:layout_alignParentLeft="true"
- android:paddingBottom="8dp" />
+ android:background="@color/white">
+
+ <TextView
+ android:id="@+id/profile_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginEnd="8dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:visibility="gone"
+ style="?attr/borderlessButtonStyle"
+ android:textAppearance="?attr/textAppearanceButton"
+ android:textColor="@color/material_deep_teal_500"
+ android:gravity="center_vertical"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:singleLine="true" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:gravity="start|center_vertical"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="8dp"
+ android:layout_below="@id/profile_button"
+ android:layout_alignParentLeft="true"
+ android:paddingBottom="8dp" />
</RelativeLayout>
<ListView
@@ -68,21 +72,23 @@
android:background="@color/white"
android:elevation="8dp"
android:nestedScrollingEnabled="true"
+ android:scrollIndicators="top|bottom"
android:divider="@null" />
- <TextView android:id="@+id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alwaysShow="true"
- android:text="@string/noApplications"
- android:padding="32dp"
- android:gravity="center"
- android:visibility="gone" />
+ <TextView
+ android:id="@+id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alwaysShow="true"
+ android:text="@string/noApplications"
+ android:padding="32dp"
+ android:gravity="center"
+ android:visibility="gone" />
<LinearLayout
android:id="@+id/button_bar"
android:visibility="gone"
- style="?android:attr/buttonBarStyle"
+ style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_ignoreOffset="true"
@@ -97,26 +103,30 @@
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:elevation="8dp">
- <Button android:id="@+id/button_once"
- android:layout_width="wrap_content"
- android:layout_gravity="start"
- android:maxLines="2"
- style="?android:attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_once"
- android:onClick="onButtonClick" />
- <Button android:id="@+id/button_always"
- android:layout_width="wrap_content"
- android:layout_gravity="end"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarPositiveButtonStyle"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_always"
- android:onClick="onButtonClick" />
+
+ <Button
+ android:id="@+id/button_once"
+ android:layout_width="wrap_content"
+ android:layout_gravity="start"
+ android:maxLines="2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick" />
+
+ <Button
+ android:id="@+id/button_always"
+ android:layout_width="wrap_content"
+ android:layout_gravity="end"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick" />
</LinearLayout>
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 31361e5..ed7ef5e 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -22,8 +22,7 @@
android:layout_height="match_parent"
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="144dp"
- android:id="@id/contentPanel"
- >
+ android:id="@id/contentPanel">
<LinearLayout
android:layout_width="match_parent"
@@ -31,66 +30,75 @@
android:layout_alwaysShow="true"
android:orientation="vertical"
android:background="@color/white"
- android:elevation="8dp" >
+ android:elevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="64dp"
- android:orientation="horizontal" >
+ android:orientation="horizontal">
- <ImageView android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="start|top"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="20dp"
- android:scaleType="fitCenter" />
- <TextView android:id="@+id/title"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingEnd="16dp" />
- <LinearLayout android:id="@+id/profile_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="4dp"
- android:layout_marginEnd="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:focusable="true"
- android:visibility="gone"
- style="?attr/borderlessButtonStyle">
- <ImageView android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="start|center_vertical"
- android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
- android:scaleType="fitCenter" />
- <TextView android:id="@id/text1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start|center_vertical"
- android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
- android:textAppearance="?attr/textAppearanceButton"
- android:textColor="?attr/textColorPrimary"
- android:minLines="1"
- android:maxLines="1"
- android:ellipsize="marquee" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="start|top"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="20dp"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="?attr/listPreferredItemHeight"
+ android:layout_marginStart="16dp"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="16dp" />
+
+ <LinearLayout
+ android:id="@+id/profile_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginEnd="4dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:focusable="true"
+ android:visibility="gone"
+ style="?attr/borderlessButtonStyle">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+ android:textAppearance="?attr/textAppearanceButton"
+ android:textColor="?attr/textColorPrimary"
+ android:minLines="1"
+ android:maxLines="1"
+ android:ellipsize="marquee" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/button_bar"
android:visibility="gone"
- style="?android:attr/buttonBarStyle"
+ style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
@@ -104,30 +112,36 @@
android:paddingEnd="12dp"
android:background="@color/white"
android:elevation="8dp">
- <Button android:id="@+id/button_once"
- android:layout_width="wrap_content"
- android:layout_gravity="start"
- android:maxLines="2"
- style="?android:attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_once"
- android:onClick="onButtonClick" />
- <Button android:id="@+id/button_always"
- android:layout_width="wrap_content"
- android:layout_gravity="end"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarPositiveButtonStyle"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_always"
- android:onClick="onButtonClick" />
+
+ <Button
+ android:id="@+id/button_once"
+ android:layout_width="wrap_content"
+ android:layout_gravity="start"
+ android:maxLines="2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick" />
+
+ <Button
+ android:id="@+id/button_always"
+ android:layout_width="wrap_content"
+ android:layout_gravity="end"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick" />
</LinearLayout>
- <View android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/dividerVertical" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/dividerVertical" />
</LinearLayout>
<ListView
@@ -140,6 +154,6 @@
android:elevation="8dp"
android:nestedScrollingEnabled="true"
android:divider="@null"
- />
+ android:scrollIndicators="top|bottom" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2ce555a..2215bd4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2420,13 +2420,11 @@
<java-symbol type="string" name="notification_hidden_text" />
<java-symbol type="string" name="notification_hidden_by_policy_text" />
<java-symbol type="id" name="app_name_text" />
- <java-symbol type="id" name="header_sub_text" />
+ <java-symbol type="id" name="header_text" />
<java-symbol type="id" name="expand_button" />
<java-symbol type="id" name="notification_header" />
- <java-symbol type="id" name="header_content_info" />
<java-symbol type="id" name="time_divider" />
- <java-symbol type="id" name="sub_text_divider" />
- <java-symbol type="id" name="content_info_divider" />
+ <java-symbol type="id" name="header_text_divider" />
<java-symbol type="id" name="text_line_1" />
<java-symbol type="drawable" name="ic_expand_notification" />
<java-symbol type="drawable" name="ic_collapse_notification" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index b7926cf..2452cfd 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1256,15 +1256,6 @@
<service android:name="android.os.BinderThreadPriorityService"
android:process=":BinderThreadPriorityService" />
- <!-- Used by ApplyOverrideConfigurationTest -->
- <activity android:name="android.app.activity.ApplyOverrideConfigurationActivity"
- android:configChanges="orientation|screenSize">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<!-- Application components used for search manager tests -->
<activity android:name="android.app.activity.SearchableActivity"
diff --git a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java
deleted file mode 100644
index 3df522d..0000000
--- a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.app.activity;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-
-public class ApplyOverrideConfigurationActivity extends Activity {
-
- @Override
- protected void attachBaseContext(Context newBase) {
- super.attachBaseContext(newBase);
-
- Configuration overrideConfig = new Configuration();
- overrideConfig.smallestScreenWidthDp = ApplyOverrideConfigurationTest.OVERRIDE_WIDTH;
- applyOverrideConfiguration(overrideConfig);
- }
-}
diff --git a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java
deleted file mode 100644
index 15ed77e..0000000
--- a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.app.activity;
-
-import android.app.UiAutomation;
-import android.content.res.Configuration;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.ActivityInstrumentationTestCase2;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ApplyOverrideConfigurationTest extends
- ActivityInstrumentationTestCase2<ApplyOverrideConfigurationActivity> {
-
- public static final int OVERRIDE_WIDTH = 9999;
-
- public ApplyOverrideConfigurationTest() {
- super(ApplyOverrideConfigurationActivity.class);
- }
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- injectInstrumentation(InstrumentationRegistry.getInstrumentation());
- getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
- }
-
- @Test
- public void testConfigurationIsOverriden() throws Exception {
- Configuration config = getActivity().getResources().getConfiguration();
- assertEquals(OVERRIDE_WIDTH, config.smallestScreenWidthDp);
-
- getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_90);
-
- config = getActivity().getResources().getConfiguration();
- assertEquals(OVERRIDE_WIDTH, config.smallestScreenWidthDp);
- }
-
- @After
- @Override
- public void tearDown() throws Exception {
- getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
- super.tearDown();
- }
-}
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index 59ffd56..eafe427 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -16,13 +16,35 @@
package android.widget;
-import android.app.Activity;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.Espresso.pressBack;
+import static android.support.test.espresso.action.ViewActions.clearText;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.replaceText;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.widget.espresso.DragHandleUtils.onHandleView;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
+import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem;
+import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed;
+import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed;
+import static android.widget.espresso.SuggestionsPopupwindowUtils.clickSuggestionsPopupItem;
+import static android.widget.espresso.SuggestionsPopupwindowUtils.onSuggestionsPopup;
+import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
+import static org.hamcrest.Matchers.is;
import android.content.res.TypedArray;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewAssertion;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
import android.text.Selection;
-import android.text.SpannableStringBuilder;
+import android.text.Spannable;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.SuggestionSpan;
@@ -42,55 +64,215 @@
super(TextViewActivity.class);
}
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getActivity();
+ }
+
+ private void setSuggestionSpan(SuggestionSpan span, int start, int end) {
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ textView.post(
+ () -> {
+ final Spannable text = (Spannable) textView.getText();
+ text.setSpan(span, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ Selection.setSelection(text, (start + end) / 2);
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
@SmallTest
- @Suppress
+ public void testOnTextContextMenuItem() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
+ getInstrumentation().waitForIdleSync();
+
+ assertSuggestionsPopupIsDisplayed();
+ }
+
+ @SmallTest
+ public void testSelectionActionMode() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.replace));
+ sleepForFloatingToolbarPopup();
+ clickFloatingToolbarItem(
+ getActivity().getString(com.android.internal.R.string.replace));
+
+ assertSuggestionsPopupIsDisplayed();
+ }
+
+ @SmallTest
+ public void testInsertionActionMode() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
+ onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.replace));
+ clickFloatingToolbarItem(
+ getActivity().getString(com.android.internal.R.string.replace));
+
+ assertSuggestionsPopupIsDisplayed();
+ }
+
+ private void showSuggestionsPopup() {
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
+ getInstrumentation().waitForIdleSync();
+ assertSuggestionsPopupIsDisplayed();
+ }
+
+ @SmallTest
+ public void testSuggestionItems() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ showSuggestionsPopup();
+
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("DEF");
+ assertSuggestionsPopupContainsItem("Def");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+
+ // Select an item.
+ clickSuggestionsPopupItem("DEF");
+ assertSuggestionsPopupIsNotDisplayed();
+ onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));
+
+ showSuggestionsPopup();
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("def");
+ assertSuggestionsPopupContainsItem("Def");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+
+ // Delete
+ clickSuggestionsPopupItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+ assertSuggestionsPopupIsNotDisplayed();
+ onView(withId(R.id.textview)).check(matches(withText("abc ghi")));
+ }
+
+ @SmallTest
+ public void testMisspelled() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_MISSPELLED);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ showSuggestionsPopup();
+
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("DEF");
+ assertSuggestionsPopupContainsItem("Def");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.addToDictionary));
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+
+ // Click "Add to dictionary".
+ clickSuggestionsPopupItem(
+ getActivity().getString(com.android.internal.R.string.addToDictionary));
+ // TODO: Check if add to dictionary dialog is displayed.
+ }
+
+ @SmallTest
+ public void testEasyCorrect() {
+ final String text = "abc def ghi";
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ new String[]{"DEF", "Def"},
+ SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
+
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("DEF");
+ assertSuggestionsPopupContainsItem("Def");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+
+ // Select an item.
+ clickSuggestionsPopupItem("DEF");
+ assertSuggestionsPopupIsNotDisplayed();
+ onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
+ assertSuggestionsPopupIsNotDisplayed();
+
+ showSuggestionsPopup();
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("def");
+ assertSuggestionsPopupContainsItem("Def");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
+ }
+
+ @SmallTest
public void testTextAppearanceInSuggestionsPopup() {
- final Activity activity = getActivity();
+ final String text = "abc def ghi";
- final String sampleText = "abc def ghi";
final String[] singleWordCandidates = {"DEF", "Def"};
- final SuggestionSpan singleWordSuggestionSpan = new SuggestionSpan(activity,
- singleWordCandidates, SuggestionSpan.FLAG_AUTO_CORRECTION);
- final int singleWordSpanStart = 4;
- final int singleWordSpanEnd = 7;
-
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
+ singleWordCandidates, SuggestionSpan.FLAG_MISSPELLED);
final String[] multiWordCandidates = {"ABC DEF GHI", "Abc Def Ghi"};
- final SuggestionSpan multiWordSuggestionSpan = new SuggestionSpan(activity,
- multiWordCandidates, SuggestionSpan.FLAG_AUTO_CORRECTION);
- final int multiWordSpanStart = 0;
- final int multiWordSpanEnd = 11;
+ final SuggestionSpan multiWordSuggestionSpan = new SuggestionSpan(getActivity(),
+ multiWordCandidates, SuggestionSpan.FLAG_MISSPELLED);
- TypedArray array = activity.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
- int id = array.getResourceId(
+ final TypedArray array =
+ getActivity().obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+ final int id = array.getResourceId(
com.android.internal.R.styleable.Theme_textEditSuggestionHighlightStyle, 0);
array.recycle();
-
- TextAppearanceSpan expectedSpan = new TextAppearanceSpan(activity, id);
- TextPaint tmpTp = new TextPaint();
+ final TextAppearanceSpan expectedSpan = new TextAppearanceSpan(getActivity(), id);
+ final TextPaint tmpTp = new TextPaint();
expectedSpan.updateDrawState(tmpTp);
final int expectedHighlightTextColor = tmpTp.getColor();
final float expectedHighlightTextSize = tmpTp.getTextSize();
-
- final EditText editText = (EditText) activity.findViewById(R.id.textview);
- final Editor editor = editText.getEditorForTesting();
- assertNotNull(editor);
-
- // Request to show SuggestionsPopupWindow.
- Runnable showSuggestionWindowRunner = new Runnable() {
- @Override
- public void run() {
- SpannableStringBuilder ssb = new SpannableStringBuilder();
- ssb.append(sampleText);
- ssb.setSpan(singleWordSuggestionSpan, singleWordSpanStart, singleWordSpanEnd,
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- ssb.setSpan(multiWordSuggestionSpan, multiWordSpanStart, multiWordSpanEnd,
- Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- editText.setText(ssb);
-
- Selection.setSelection(editText.getText(), singleWordSpanStart, singleWordSpanEnd);
- editText.onTextContextMenuItem(TextView.ID_REPLACE);
- }
- };
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
// In this test, the SuggestionsPopupWindow looks like
// abc def ghi
@@ -103,96 +285,74 @@
// | DELETE |
// -----------------
// *XX* means that XX is highlighted.
- Runnable popupVaridator = new Runnable() {
- @Override
- public void run() {
- Editor.SuggestionsPopupWindow popupWindow =
- editor.getSuggestionsPopupWindowForTesting();
- assertNotNull(popupWindow);
+ for (int i = 0; i < 2; i++) {
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
+ setSuggestionSpan(multiWordSuggestionSpan, 0, text.length());
- LinearLayout linearLayout = (LinearLayout) popupWindow.getContentViewForTesting();
- assertNotNull(linearLayout);
+ showSuggestionsPopup();
+ assertSuggestionsPopupIsDisplayed();
+ assertSuggestionsPopupContainsItem("abc DEF ghi");
+ assertSuggestionsPopupContainsItem("abc Def ghi");
+ assertSuggestionsPopupContainsItem("ABC DEF GHI");
+ assertSuggestionsPopupContainsItem("Abc Def Ghi");
+ assertSuggestionsPopupContainsItem(
+ getActivity().getString(com.android.internal.R.string.delete));
- ListView listView = (ListView)linearLayout.findViewById(
- com.android.internal.R.id.suggestionContainer);
- assertNotNull(listView);
+ onSuggestionsPopup().check(new ViewAssertion() {
+ @Override
+ public void check(View view, NoMatchingViewException e) {
+ final ListView listView = (ListView) view.findViewById(
+ com.android.internal.R.id.suggestionContainer);
+ assertNotNull(listView);
+ final int childNum = listView.getChildCount();
+ assertEquals(singleWordCandidates.length + multiWordCandidates.length,
+ childNum);
- int childNum = listView.getChildCount();
- assertEquals(singleWordCandidates.length + multiWordCandidates.length, childNum);
+ for (int j = 0; j < childNum; j++) {
+ final TextView suggestion = (TextView) listView.getChildAt(j);
+ assertNotNull(suggestion);
+ final Spanned spanned = (Spanned) suggestion.getText();
+ assertNotNull(spanned);
- for (int i = 0; i < singleWordCandidates.length; ++i) {
- TextView textView = (TextView) listView.getChildAt(i);
- assertNotNull(textView);
+ // Check that the suggestion item order is kept.
+ final String expectedText;
+ if (j < singleWordCandidates.length) {
+ expectedText = "abc " + singleWordCandidates[j] + " ghi";
+ } else {
+ expectedText = multiWordCandidates[j - singleWordCandidates.length];
+ }
+ assertEquals(expectedText, spanned.toString());
- Spanned spanned = (Spanned) textView.getText();
- assertNotNull(spanned);
+ // Check that the text is highlighted with correct color and text size.
+ final TextAppearanceSpan[] taSpan = spanned.getSpans(
+ text.indexOf('d'), text.indexOf('f') + 1, TextAppearanceSpan.class);
+ assertEquals(1, taSpan.length);
+ TextPaint tp = new TextPaint();
+ taSpan[0].updateDrawState(tp);
+ assertEquals(expectedHighlightTextColor, tp.getColor());
+ assertEquals(expectedHighlightTextSize, tp.getTextSize());
- // Check that the suggestion item order is kept.
- String expectedText = "abc " + singleWordCandidates[i] + " ghi";
- assertEquals(expectedText, spanned.toString());
-
- // Check that the text is highlighted with correct color and text size.
- TextAppearanceSpan[] taSpan = spanned.getSpans(singleWordSpanStart,
- singleWordSpanEnd, TextAppearanceSpan.class);
- assertEquals(1, taSpan.length);
- TextPaint tp = new TextPaint();
- taSpan[0].updateDrawState(tp);
- assertEquals(expectedHighlightTextColor, tp.getColor());
- assertEquals(expectedHighlightTextSize, tp.getTextSize());
-
- // Check only center word is highlighted.
- assertEquals(singleWordSpanStart, spanned.getSpanStart(taSpan[0]));
- assertEquals(singleWordSpanEnd, spanned.getSpanEnd(taSpan[0]));
+ // Check the correct part of the text is highlighted.
+ final int expectedStart;
+ final int expectedEnd;
+ if (j < singleWordCandidates.length) {
+ expectedStart = text.indexOf('d');
+ expectedEnd = text.indexOf('f') + 1;
+ } else {
+ expectedStart = 0;
+ expectedEnd = text.length();
+ }
+ assertEquals(expectedStart, spanned.getSpanStart(taSpan[0]));
+ assertEquals(expectedEnd, spanned.getSpanEnd(taSpan[0]));
+ }
}
-
- for (int i = 0; i < multiWordCandidates.length; ++i) {
- int indexInListView = singleWordCandidates.length + i;
- TextView textView = (TextView) listView.getChildAt(indexInListView);
- assertNotNull(textView);
-
- Spanned spanned = (Spanned) textView.getText();
- assertNotNull(spanned);
-
- // Check that the suggestion item order is kept.
- assertEquals(multiWordCandidates[i], spanned.toString());
-
- // Check that the text is highlighted with correct color and text size.
- TextAppearanceSpan[] taSpan = spanned.getSpans(
- 0, multiWordCandidates[i].length(), TextAppearanceSpan.class);
- assertEquals(1, taSpan.length);
- TextPaint tp = new TextPaint();
- taSpan[0].updateDrawState(tp);
- assertEquals(expectedHighlightTextColor, tp.getColor());
- assertEquals(expectedHighlightTextSize, tp.getTextSize());
-
- // Check the whole text is highlighted.
- assertEquals(multiWordSpanStart, spanned.getSpanStart(taSpan[0]));
- assertEquals(multiWordSpanEnd, spanned.getSpanEnd(taSpan[0]));
- }
-
- TextView deleteButton = (TextView)linearLayout.findViewById(
- com.android.internal.R.id.deleteButton);
- assertEquals(View.VISIBLE, deleteButton.getWindowVisibility());
- }
- };
-
- // Show the SuggestionWindow and verify the contents.
- activity.runOnUiThread(showSuggestionWindowRunner);
- getInstrumentation().waitForIdleSync();
- activity.runOnUiThread(popupVaridator);
-
- // Request to hide the SuggestionPopupWindow and wait until it is hidden.
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- editText.setText("");
- }
- });
- getInstrumentation().waitForIdleSync();
-
- // Show and verify the contents again.
- activity.runOnUiThread(showSuggestionWindowRunner);
- getInstrumentation().waitForIdleSync();
- activity.runOnUiThread(popupVaridator);
+ });
+ pressBack();
+ onView(withId(R.id.textview))
+ .inRoot(withDecorView(is(getActivity().getWindow().getDecorView())))
+ .perform(clearText());
+ }
}
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index 923b829..edb749b95 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,6 +16,10 @@
package android.widget;
+
+import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled;
+import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled;
+import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed;
import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
@@ -41,11 +45,9 @@
import com.android.frameworks.coretests.R;
-import android.support.test.espresso.Espresso;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.MotionEvent;
-import android.widget.espresso.ContextMenuUtils;
/**
* Tests mouse interaction of the TextView widget from an Activity
@@ -57,7 +59,8 @@
}
@Override
- public void setUp() {
+ public void setUp() throws Exception {
+ super.setUp();
getActivity();
}
@@ -102,28 +105,28 @@
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
- ContextMenuUtils.assertContextMenuIsNotDisplayed();
+ assertContextMenuIsNotDisplayed();
onView(withId(R.id.textview)).perform(
mouseClickOnTextAtIndex(text.indexOf("d"), MotionEvent.BUTTON_SECONDARY));
- ContextMenuUtils.assertContextMenuContainsItemDisabled(
+ assertContextMenuContainsItemDisabled(
getActivity().getString(com.android.internal.R.string.copy));
- ContextMenuUtils.assertContextMenuContainsItemEnabled(
+ assertContextMenuContainsItemEnabled(
getActivity().getString(com.android.internal.R.string.undo));
// Hide context menu.
pressBack();
- ContextMenuUtils.assertContextMenuIsNotDisplayed();
+ assertContextMenuIsNotDisplayed();
onView(withId(R.id.textview)).perform(
mouseDragOnText(text.indexOf("c"), text.indexOf("h")));
onView(withId(R.id.textview)).perform(
mouseClickOnTextAtIndex(text.indexOf("d"), MotionEvent.BUTTON_SECONDARY));
- ContextMenuUtils.assertContextMenuContainsItemEnabled(
+ assertContextMenuContainsItemEnabled(
getActivity().getString(com.android.internal.R.string.copy));
- ContextMenuUtils.assertContextMenuContainsItemEnabled(
+ assertContextMenuContainsItemEnabled(
getActivity().getString(com.android.internal.R.string.undo));
// Hide context menu.
@@ -133,9 +136,9 @@
onView(withId(R.id.textview)).perform(
mouseClickOnTextAtIndex(text.indexOf("i"), MotionEvent.BUTTON_SECONDARY));
- ContextMenuUtils.assertContextMenuContainsItemDisabled(
+ assertContextMenuContainsItemDisabled(
getActivity().getString(com.android.internal.R.string.copy));
- ContextMenuUtils.assertContextMenuContainsItemEnabled(
+ assertContextMenuContainsItemEnabled(
getActivity().getString(com.android.internal.R.string.undo));
// Hide context menu.
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index ecf88f1..67ffd2b 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -19,7 +19,6 @@
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.onFloatingToolBarItem;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
@@ -31,9 +30,10 @@
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.pressKey;
@@ -67,7 +67,8 @@
}
@Override
- public void setUp() {
+ public void setUp() throws Exception {
+ super.setUp();
getActivity();
}
@@ -256,7 +257,8 @@
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView("test"));
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
- onFloatingToolBarItem(withText(com.android.internal.R.string.cut)).perform(click());
+ clickFloatingToolbarItem(
+ getActivity().getString(com.android.internal.R.string.cut));
onView(withId(R.id.textview)).perform(longClick());
sleepForFloatingToolbarPopup();
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index 0f7f359..838f4db 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -17,6 +17,7 @@
package android.widget.espresso;
import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
@@ -24,6 +25,7 @@
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withTagValue;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
@@ -34,8 +36,6 @@
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewInteraction;
-import android.support.test.espresso.action.ViewActions;
-import android.support.test.espresso.matcher.ViewMatchers;
import android.view.View;
import com.android.internal.widget.FloatingToolbar;
@@ -90,7 +90,7 @@
final int id = com.android.internal.R.id.overflow;
onView(allOf(withId(id), isDisplayed()))
.inRoot(withDecorView(hasDescendant(withId(id))))
- .perform(ViewActions.click());
+ .perform(click());
onView(isRoot()).perform(SLEEP);
}
@@ -106,7 +106,7 @@
*/
public static void assertFloatingToolbarContainsItem(String itemLabel) {
try{
- onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+ onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
} catch (AssertionError e) {
try{
toggleOverflow();
@@ -115,7 +115,7 @@
throw e;
}
try{
- onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+ onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
} finally {
toggleOverflow();
}
@@ -138,6 +138,21 @@
}
/**
+ * Click specified item on the floating tool bar.
+ *
+ * @param itemLabel label of the item.
+ */
+ public static void clickFloatingToolbarItem(String itemLabel) {
+ try{
+ onFloatingToolBarItem(withText(itemLabel)).check(matches(isDisplayed()));
+ } catch (AssertionError e) {
+ // Try to find the item in the overflow menu.
+ toggleOverflow();
+ }
+ onFloatingToolBarItem(withText(itemLabel)).perform(click());
+ }
+
+ /**
* ViewAction to sleep to wait floating toolbar's animation.
*/
private static final ViewAction SLEEP = new ViewAction() {
diff --git a/core/tests/coretests/src/android/widget/espresso/SuggestionsPopupwindowUtils.java b/core/tests/coretests/src/android/widget/espresso/SuggestionsPopupwindowUtils.java
new file mode 100644
index 0000000..b5a96ae
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/SuggestionsPopupwindowUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.espresso.action.GeneralLocation;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Tap;
+import android.view.View;
+
+public final class SuggestionsPopupwindowUtils {
+ private static final int id = com.android.internal.R.id.suggestionWindowContainer;
+
+ private SuggestionsPopupwindowUtils() {};
+
+ public static ViewInteraction onSuggestionsPopup() {
+ return onView(withId(id)).inRoot(withDecorView(hasDescendant(withId(id))));
+ }
+
+ private static ViewInteraction onSuggestionsPopupItem(Matcher<View> matcher) {
+ return onView(matcher).inRoot(withDecorView(hasDescendant(withId(id))));
+ }
+
+ /**
+ * Asserts that the suggestions popup is displayed on screen.
+ *
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertSuggestionsPopupIsDisplayed() {
+ onSuggestionsPopup().check(matches(isDisplayed()));
+ }
+
+ /**
+ * Asserts that the suggestions popup is not displayed on screen.
+ *
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertSuggestionsPopupIsNotDisplayed() {
+ try {
+ onSuggestionsPopup().check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ return;
+ }
+ throw new AssertionError("Suggestions popup is displayed");
+ }
+
+ /**
+ * Asserts that the suggestions popup contains the specified item.
+ *
+ * @param itemLabel label of the item.
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertSuggestionsPopupContainsItem(String itemLabel) {
+ onSuggestionsPopupItem(withText(itemLabel)).check(matches(isDisplayed()));
+ }
+
+ /**
+ * Click on the specified item in the suggestions popup.
+ *
+ * @param itemLabel label of the item.
+ */
+ public static void clickSuggestionsPopupItem(String itemLabel) {
+ onSuggestionsPopupItem(withText(itemLabel)).perform(new SuggestionItemClickAction());
+ }
+
+ /**
+ * Click action to avoid checking ViewClickAction#getConstraints().
+ * TODO: Use Espresso.onData instead of this.
+ */
+ private static final class SuggestionItemClickAction implements ViewAction {
+ private final ViewClickAction mViewClickAction;
+
+ public SuggestionItemClickAction() {
+ mViewClickAction =
+ new ViewClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER, Press.FINGER);
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return mViewClickAction.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ mViewClickAction.perform(uiController, view);
+ }
+ }
+}
diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp
index 537b56d..d07942b 100644
--- a/media/jni/android_media_MediaDataSource.cpp
+++ b/media/jni/android_media_MediaDataSource.cpp
@@ -155,4 +155,8 @@
return 0;
}
+String8 JMediaDataSource::toString() {
+ return String8::format("JMediaDataSource(pid %d, uid %d)", getpid(), getuid());
+}
+
} // namespace android
diff --git a/media/jni/android_media_MediaDataSource.h b/media/jni/android_media_MediaDataSource.h
index 34624eb..378baf4 100644
--- a/media/jni/android_media_MediaDataSource.h
+++ b/media/jni/android_media_MediaDataSource.h
@@ -46,6 +46,7 @@
virtual status_t getSize(off64_t* size);
virtual void close();
virtual uint32_t getFlags();
+ virtual String8 toString();
private:
// Protect all member variables with mLock because this object will be
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 1660e26..cf0643d 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -31,6 +31,7 @@
<color name="accent">@*android:color/accent_material_light</color>
<color name="accent_dark">@*android:color/accent_material_dark</color>
<color name="action_mode">@color/material_grey_400</color>
+ <color name="status_bar_color">@*android:color/material_blue_grey_950</color>
<color name="band_select_background">#88ffffff</color>
<color name="band_select_border">#44000000</color>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index b0996aa..9f09ebc 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -30,6 +30,7 @@
<item name="android:colorAccent">@color/accent</item>
<item name="colorActionMode">@color/action_mode</item>
<item name="android:queryBackground">@color/menu_search_background</item>
+ <item name="android:statusBarColor">@color/status_bar_color</item>
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0d6ddf2..4ee37a5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -101,16 +101,6 @@
@CallSuper
@Override
public void onCreate(Bundle icicle) {
- // This flag is being set here as a result of the bug. When the flag was set in the
- // styles.xml keyboard was messing the layout of dialogs (create dir, rename).
- // Attempts were made to keep the flag in the main theme and to override it in the dialog
- // layout xml or to create separate style for dialog and assign it in styles.xml.
- // None of this brought successful results.
- // Setting the flag works here most probably because of the timing when it is set. Also the
- // setting might not affect the dialogs that are created in new windows or it affects them
- // in the different way that having this in the style.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
// Record the time when onCreate is invoked for metric.
mStartTime = new Date().getTime();
@@ -140,7 +130,6 @@
mSearchManager = new SearchViewManager(this, icicle);
DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
- Display.adjustToolbar(toolbar, this);
setActionBar(toolbar);
mNavigator = new NavigationView(
mDrawer,
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Display.java b/packages/DocumentsUI/src/com/android/documentsui/Display.java
index d46a3ea..8b13222 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Display.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Display.java
@@ -20,8 +20,6 @@
import android.content.Context;
import android.graphics.Point;
import android.util.TypedValue;
-import android.view.WindowManager;
-import android.widget.Toolbar;
/*
* Convenience class for getting display related attributes
@@ -47,41 +45,12 @@
* Returns action bar height in raw pixels.
*/
public static float actionBarHeight(Context context) {
- int height = 0;
+ int actionBarHeight = 0;
TypedValue tv = new TypedValue();
if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
- height = TypedValue.complexToDimensionPixelSize(tv.data,
+ actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
context.getResources().getDisplayMetrics());
}
- return height;
- }
-
- /*
- * Returns status bar height in raw pixels.
- */
- private static int statusBarHeight(Context context) {
- int height = 0;
- int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen",
- "android");
- if (resourceId > 0) {
- height = context.getResources().getDimensionPixelSize(resourceId);
- }
- return height;
- }
-
- /*
- * Adjusts toolbar for the layout with translucent status bar. Increases the
- * height of the toolbar and adds padding at the top to accommodate status bar visible above
- * toolbar.
- */
- public static void adjustToolbar(Toolbar toolbar, Activity activity) {
- if ((activity.getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
- int statusBarHeight = Display.statusBarHeight(activity);
- toolbar.getLayoutParams().height = (int) (Display.actionBarHeight(activity)
- + statusBarHeight);
- toolbar.setPadding(toolbar.getPaddingLeft(), statusBarHeight, toolbar.getPaddingRight(),
- toolbar.getPaddingBottom());
- }
+ return actionBarHeight;
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 7a4099a..14e6b69 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -84,7 +84,7 @@
View drawer = activity.findViewById(R.id.drawer_roots);
Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
- Display.adjustToolbar(toolbar, activity);
+
drawer.getLayoutParams().width = calculateDrawerWidth(activity);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index c7d60e3..f239eb4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -123,9 +123,6 @@
/** Instance state for every shown directory */
public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
- /** UI selection */
- public Selection selectedDocuments = new Selection();
-
/** Currently copying file */
public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<>();
@@ -202,7 +199,6 @@
out.writeInt(external ? 1 : 0);
DurableUtils.writeToParcel(out, stack);
out.writeMap(dirState);
- out.writeParcelable(selectedDocuments, 0);
out.writeList(selectedDocumentsForCopy);
out.writeList(excludedAuthorities);
out.writeInt(openableOnly ? 1 : 0);
@@ -233,7 +229,6 @@
state.external = in.readInt() != 0;
DurableUtils.readFromParcel(in, state.stack);
in.readMap(state.dirState, loader);
- state.selectedDocuments = in.readParcelable(loader);
in.readList(state.selectedDocumentsForCopy, loader);
in.readList(state.excludedAuthorities, loader);
state.openableOnly = in.readInt() != 0;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 630f359..1c85a8a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -45,6 +45,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -226,7 +227,8 @@
mStateKey = buildStateKey(mRoot, mDocument);
mQuery = args.getString(Shared.EXTRA_QUERY);
mType = args.getInt(Shared.EXTRA_TYPE);
- mSelection = args.getParcelable(Shared.EXTRA_SELECTION);
+ final Selection selection = args.getParcelable(Shared.EXTRA_SELECTION);
+ mSelection = selection != null ? selection : new Selection();
mSearchMode = args.getBoolean(Shared.EXTRA_SEARCH_MODE);
mIconHelper = new IconHelper(context, MODE_GRID);
@@ -290,14 +292,25 @@
outState.putParcelable(Shared.EXTRA_ROOT, mRoot);
outState.putParcelable(Shared.EXTRA_DOC, mDocument);
outState.putString(Shared.EXTRA_QUERY, mQuery);
- outState.putParcelable(Shared.EXTRA_SELECTION, mSelection);
- outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
+ // Workaround. To avoid crash, write only up to 512 KB of selection.
+ // If more files are selected, then the selection will be lost.
+ final Parcel parcel = Parcel.obtain();
+ try {
+ mSelection.writeToParcel(parcel, 0);
+ if (parcel.dataSize() <= 512 * 1024) {
+ outState.putParcelable(Shared.EXTRA_SELECTION, mSelection);
+ }
+ } finally {
+ parcel.recycle();
+ }
+
+ outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
}
@Override
public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
- switch(requestCode) {
+ switch (requestCode) {
case REQUEST_COPY_DESTINATION:
handleCopyResult(resultCode, data);
break;
@@ -490,24 +503,19 @@
@Override
public void onSelectionChanged() {
mSelectionManager.getSelection(mSelected);
- TypedValue color = new TypedValue();
if (mSelected.size() > 0) {
if (DEBUG) Log.d(TAG, "Maybe starting action mode.");
if (mActionMode == null) {
if (DEBUG) Log.d(TAG, "Yeah. Starting action mode.");
mActionMode = getActivity().startActionMode(this);
}
- getActivity().getTheme().resolveAttribute(R.attr.colorActionMode, color, true);
updateActionMenu();
} else {
if (DEBUG) Log.d(TAG, "Finishing action mode.");
if (mActionMode != null) {
mActionMode.finish();
}
- getActivity().getTheme().resolveAttribute(
- android.R.attr.colorPrimaryDark, color, true);
}
- getActivity().getWindow().setStatusBarColor(color.data);
if (mActionMode != null) {
final String title = Shared.getQuantityString(getActivity(),
diff --git a/packages/Keyguard/res/drawable/ic_backspace_24dp.xml b/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
index 47c8d14..1e4022e 100644
--- a/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
+++ b/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
@@ -15,6 +15,7 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
+ android:autoMirrored="true"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
diff --git a/packages/Keyguard/res/values/attrs.xml b/packages/Keyguard/res/values/attrs.xml
index 96a5bcc..7cfe631 100644
--- a/packages/Keyguard/res/values/attrs.xml
+++ b/packages/Keyguard/res/values/attrs.xml
@@ -32,6 +32,9 @@
<declare-styleable name="PasswordTextView">
<attr name="scaledTextSize" format="integer" />
+ <attr name="android:gravity" />
+ <attr name="dotSize" format="dimension" />
+ <attr name="charPadding" format="dimension" />
</declare-styleable>
<declare-styleable name="CarrierText">
diff --git a/packages/Keyguard/src/com/android/keyguard/NumPadKey.java b/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
index ef8bb0b..2ff7e12 100644
--- a/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
+++ b/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
@@ -71,6 +71,10 @@
}
public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, R.layout.keyguard_num_pad_key);
+ }
+
+ protected NumPadKey(Context context, AttributeSet attrs, int defStyle, int contentResource) {
super(context, attrs, defStyle);
setFocusable(true);
@@ -92,7 +96,7 @@
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.keyguard_num_pad_key, this, true);
+ inflater.inflate(contentResource, this, true);
mDigitText = (TextView) findViewById(R.id.digit_text);
mDigitText.setText(Integer.toString(mDigit));
@@ -113,7 +117,11 @@
}
}
- setBackground(mContext.getDrawable(R.drawable.ripple_drawable));
+ a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+ if (!a.hasValueOrEmpty(android.R.styleable.View_background)) {
+ setBackground(mContext.getDrawable(R.drawable.ripple_drawable));
+ }
+ a.recycle();
setContentDescription(mDigitText.getText().toString());
}
diff --git a/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java b/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
index 50e7ecb..6eea81b 100644
--- a/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
@@ -33,6 +33,7 @@
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -81,6 +82,7 @@
* The raw text size, will be multiplied by the scaled density when drawn
*/
private final int mTextHeightRaw;
+ private final int mGravity;
private ArrayList<CharState> mTextChars = new ArrayList<>();
private String mText = "";
private Stack<CharState> mCharPool = new Stack<>();
@@ -118,6 +120,12 @@
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
try {
mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
+ mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
+ mDotSize = a.getDimensionPixelSize(R.styleable.PasswordTextView_dotSize,
+ getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size));
+ mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.password_char_padding));
} finally {
a.recycle();
}
@@ -125,9 +133,6 @@
mDrawPaint.setTextAlign(Paint.Align.CENTER);
mDrawPaint.setColor(0xffffffff);
mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
- mDotSize = getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size);
- mCharPadding = getContext().getResources().getDimensionPixelSize(R.dimen
- .password_char_padding);
mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
@@ -142,11 +147,23 @@
@Override
protected void onDraw(Canvas canvas) {
float totalDrawingWidth = getDrawingWidth();
- float currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+ float currentDrawPosition;
+ if ((mGravity & Gravity.START) != 0) {
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ currentDrawPosition = getWidth() - getPaddingRight() - totalDrawingWidth;
+ } else {
+ currentDrawPosition = getPaddingLeft();
+ }
+ } else {
+ currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+ }
int length = mTextChars.size();
Rect bounds = getCharBounds();
int charHeight = (bounds.bottom - bounds.top);
- float yPosition = getHeight() / 2;
+ float yPosition =
+ (getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
+ canvas.clipRect(getPaddingLeft(), getPaddingTop(),
+ getWidth()-getPaddingRight(), getHeight()-getPaddingBottom());
float charLength = bounds.right - bounds.left;
for (int i = 0; i < length; i++) {
CharState charState = mTextChars.get(i);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index e86ca82..59637be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -459,58 +459,40 @@
LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
EnforcedAdmin enforcedAdmin = null;
final int userId = UserHandle.myUserId();
- if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
- // userId is managed profile and has a separate challenge, only consider
- // the admins in that user.
- final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId);
+ final UserManager um = UserManager.get(context);
+ final List<UserInfo> profiles = um.getProfiles(userId);
+ final int profilesSize = profiles.size();
+ // As we do not have a separate screen lock timeout settings for work challenge,
+ // we need to combine all profiles maximum time to lock even work challenge is
+ // enabled.
+ for (int i = 0; i < profilesSize; i++) {
+ final UserInfo userInfo = profiles.get(i);
+ final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
if (admins == null) {
- return null;
+ continue;
}
for (ComponentName admin : admins) {
- if (dpm.getMaximumTimeToLock(admin, userId) > 0) {
+ if (dpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userId);
+ enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
} else {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
- }
- }
- } else {
- // Return all admins for this user and the profiles that are visible from this
- // user that do not use a separate work challenge.
- final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
- for (UserInfo userInfo : um.getProfiles(userId)) {
- final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id);
- if (admins == null) {
+ // This same admins could have set policies both on the managed profile
+ // and on the parent. So, if the admin has set the policy on the
+ // managed profile here, we don't need to further check if that admin
+ // has set policy on the parent admin.
continue;
}
- final boolean isSeparateProfileChallengeEnabled =
- lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id);
- for (ComponentName admin : admins) {
- if (!isSeparateProfileChallengeEnabled) {
- if (dpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
- if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
- } else {
- return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
- }
- // This same admins could have set policies both on the managed profile
- // and on the parent. So, if the admin has set the policy on the
- // managed profile here, we don't need to further check if that admin
- // has set policy on the parent admin.
- continue;
- }
- }
- if (userInfo.isManagedProfile()) {
- // If userInfo.id is a managed profile, we also need to look at
- // the policies set on the parent.
- DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo);
- if (parentDpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
- if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
- } else {
- return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
- }
+ if (userInfo.isManagedProfile()) {
+ // If userInfo.id is a managed profile, we also need to look at
+ // the policies set on the parent.
+ final DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo);
+ if (parentDpm.getMaximumTimeToLock(admin, userInfo.id) > 0) {
+ if (enforcedAdmin == null) {
+ enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+ } else {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index c131fa5..7ca7614 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -463,7 +463,6 @@
.setContentTitle(title)
.setTicker(title)
.setContentText(name)
- .setContentInfo(percentageText)
.setProgress(info.max, info.progress, false)
.setOngoing(true)
.setContentIntent(infoPendingIntent)
@@ -958,7 +957,7 @@
.setDeleteIntent(newCancelIntent(context, info));
if (!TextUtils.isEmpty(info.name)) {
- builder.setContentInfo(info.name);
+ builder.setSubText(info.name);
}
Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_backspace.xml b/packages/SystemUI/res/drawable/ic_ksh_key_backspace.xml
new file mode 100644
index 0000000..6519673
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_backspace.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:pathData="M0 0h24v24H0z" />
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M22 3H7c-.69 0-1.23 .35 -1.59 .88 L0 12l5.41 8.11c.36 .53 .9 .89
+1.59 .89 h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 12.59L17.59 17 14 13.41 10.41 17 9 15.59
+12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_down.xml b/packages/SystemUI/res/drawable/ic_ksh_key_down.xml
new file mode 100644
index 0000000..25a2560
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+ <path android:pathData="M0-.75h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_enter.xml b/packages/SystemUI/res/drawable/ic_ksh_key_enter.xml
new file mode 100644
index 0000000..599f350
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_enter.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:pathData="M0 0h24v24H0z" />
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_left.xml b/packages/SystemUI/res/drawable/ic_ksh_key_left.xml
new file mode 100644
index 0000000..038187e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_left.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
+ <path android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml
new file mode 100644
index 0000000..1e2195e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91
+3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27 .28 v.79l5 4.99L20.49
+19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5
+14z" />
+ <path android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_right.xml b/packages/SystemUI/res/drawable/ic_ksh_key_right.xml
new file mode 100644
index 0000000..f2d7315
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
+ <path android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_up.xml b/packages/SystemUI/res/drawable/ic_ksh_key_up.xml
new file mode 100644
index 0000000..36a83b1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ksh_key_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="@color/ksh_key_item_color"
+ android:pathData="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" />
+ <path android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml
index af3e379..8e7feec94 100644
--- a/packages/SystemUI/res/layout/battery_detail.xml
+++ b/packages/SystemUI/res/layout/battery_detail.xml
@@ -26,10 +26,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="72dp"
- android:paddingBottom="@dimen/battery_detail_graph_space_top"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/colorAccent" />
+ <com.android.systemui.ResizingSpace
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/battery_detail_graph_space_top" />
+
<com.android.settingslib.graph.UsageView
android:id="@+id/battery_usage"
android:layout_width="match_parent"
@@ -40,11 +43,14 @@
android:colorAccent="?android:attr/colorAccent"
systemui:textColor="#66FFFFFF" />
+ <com.android.systemui.ResizingSpace
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/battery_detail_graph_space_bottom" />
+
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
- android:layout_marginTop="@dimen/battery_detail_graph_space_bottom"
android:layout_marginBottom="8dp" />
<RelativeLayout
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml
new file mode 100644
index 0000000..0cecb96
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginStart="@dimen/ksh_item_margin_start"
+ android:scaleType="fitXY"
+ android:background="@color/ksh_key_item_background"/>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
index 5002c12..1215029 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml
@@ -17,9 +17,9 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="4dp"
- android:padding="4dp"
- android:background="#EEEEEE"
- android:textColor="#8C000000"
+ android:padding="@dimen/ksh_item_padding"
+ android:layout_marginStart="@dimen/ksh_item_margin_start"
+ android:background="@color/ksh_key_item_background"
+ android:textColor="@color/ksh_key_item_color"
android:singleLine="true"
- android:textSize="14sp"/>
+ android:textSize="@dimen/ksh_item_text_size"/>
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 3358a18..af2a285 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -25,13 +25,15 @@
android:visibility="invisible"
android:orientation="vertical">
+ <com.android.systemui.ResizingSpace
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_detail_margin_top" />
+
<include
android:id="@+id/qs_detail_header"
layout="@layout/qs_detail_header"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="28dp"
- />
+ android:layout_height="wrap_content" />
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/qs_detail_header_progress"
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index c22e42c..f1a8d63 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -16,17 +16,19 @@
-->
<!-- extends FrameLayout -->
<com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/qs_detail_items_padding_top"
android:paddingStart="16dp"
android:paddingEnd="16dp">
- <LinearLayout
+ <com.android.systemui.qs.AutoSizingList
android:id="@android:id/list"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" />
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ sysui:itemHeight="@dimen/qs_detail_item_height" />
<LinearLayout
android:id="@android:id/empty"
@@ -48,9 +50,4 @@
android:layout_marginTop="20dp"
android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
</LinearLayout>
-
- <View
- android:id="@+id/min_height_spacer"
- android:layout_width="match_parent"
- android:layout_height="0dp"/>
-</com.android.systemui.qs.QSDetailItems>
\ No newline at end of file
+</com.android.systemui.qs.QSDetailItems>
diff --git a/packages/SystemUI/res/values-h560dp/config.xml b/packages/SystemUI/res/values-h560dp/config.xml
deleted file mode 100644
index 8b576b9..0000000
--- a/packages/SystemUI/res/values-h560dp/config.xml
+++ /dev/null
@@ -1,23 +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
- -->
-
-<resources>
- <!-- The maximum number of items to be displayed in quick settings -->
- <integer name="quick_settings_detail_max_item_count">6</integer>
-</resources>
-
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 26a81c8..c40797c 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -24,4 +24,14 @@
<dimen name="docked_divider_handle_width">2dp</dimen>
<dimen name="docked_divider_handle_height">16dp</dimen>
+
+ <dimen name="qs_tile_margin_top">2dp</dimen>
+ <dimen name="qs_brightness_padding_top">0dp</dimen>
+
+ <dimen name="battery_detail_graph_space_top">9dp</dimen>
+ <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
+
+ <integer name="quick_settings_num_columns">4</integer>
+ <bool name="quick_settings_wide">true</bool>
+ <dimen name="qs_detail_margin_top">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw410dp/config.xml b/packages/SystemUI/res/values-sw410dp/config.xml
new file mode 100644
index 0000000..08b2f88
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <integer name="quick_settings_num_rows">2</integer>
+</resources>
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
new file mode 100644
index 0000000..5ce6524
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <dimen name="qs_detail_items_padding_top">16dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw540dp/config.xml b/packages/SystemUI/res/values-sw540dp/config.xml
new file mode 100644
index 0000000..e554fc6d
--- /dev/null
+++ b/packages/SystemUI/res/values-sw540dp/config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <integer name="quick_settings_num_rows">3</integer>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 4ed15d5..49a7a29 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -40,4 +40,8 @@
<dimen name="battery_detail_graph_space_top">27dp</dimen>
<dimen name="battery_detail_graph_space_bottom">27dp</dimen>
+
+ <dimen name="qs_tile_margin_top">16dp</dimen>
+ <dimen name="qs_brightness_padding_top">6dp</dimen>
+ <dimen name="qs_detail_margin_top">28dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml
index cd17bed..4160c83 100644
--- a/packages/SystemUI/res/values-w550dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml
@@ -20,7 +20,4 @@
<dimen name="notification_panel_width">544dp</dimen>
<dimen name="qs_expand_margin">32dp</dimen>
-
- <dimen name="battery_detail_graph_space_top">9dp</dimen>
- <dimen name="battery_detail_graph_space_bottom">9dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 1e979fd..6dd8c52 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -97,5 +97,9 @@
<declare-styleable name="DensityContainer">
<attr name="android:layout" />
</declare-styleable>
+
+ <declare-styleable name="AutoSizingList">
+ <attr name="itemHeight" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b9aa26b..b874e7c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -169,6 +169,8 @@
<color name="ksh_system_group_color">#ff00bcd4</color>
<color name="ksh_application_group_color">#fff44336</color>
<color name="ksh_keyword_color">#d9000000</color>
+ <color name="ksh_key_item_color">@color/material_grey_600</color>
+ <color name="ksh_key_item_background">#eeeeee</color>
<!-- Background color of edit overflow -->
<color name="qs_edit_overflow_bg">#455A64</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6ce2a5d..42798a4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -92,11 +92,8 @@
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>
- <!-- The maximum number of rows in the QuickSettings -->
- <integer name="quick_settings_max_rows">4</integer>
-
- <!-- The maximum number of rows in the QuickSettings when on the keyguard -->
- <integer name="quick_settings_max_rows_keyguard">3</integer>
+ <!-- The number of rows in the QuickSettings -->
+ <integer name="quick_settings_num_rows">1</integer>
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">1</integer>
@@ -116,9 +113,6 @@
<integer name="quick_settings_brightness_dialog_short_timeout">2000</integer>
<integer name="quick_settings_brightness_dialog_long_timeout">4000</integer>
- <!-- The maximum number of items to be displayed in quick settings -->
- <integer name="quick_settings_detail_max_item_count">5</integer>
-
<!-- Should "4G" be shown instead of "LTE" when the network is NETWORK_TYPE_LTE? -->
<bool name="config_show4GForLTE">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c094da9..a4eadbf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -172,6 +172,7 @@
<dimen name="qs_tile_height">88dp</dimen>
<dimen name="qs_tile_margin">16dp</dimen>
+ <dimen name="qs_tile_margin_top">16dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_date_anim_translation">32dp</dimen>
@@ -201,10 +202,12 @@
<dimen name="qs_detail_item_primary_text_size">16sp</dimen>
<dimen name="qs_detail_item_secondary_text_size">14sp</dimen>
<dimen name="qs_detail_empty_text_size">14sp</dimen>
+ <dimen name="qs_detail_margin_top">28dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
<dimen name="qs_expand_margin">0dp</dimen>
<dimen name="qs_battery_padding">2dp</dimen>
+ <dimen name="qs_detail_items_padding_top">4dp</dimen>
<dimen name="segmented_button_spacing">0dp</dimen>
<dimen name="borderless_button_radius">2dp</dimen>
@@ -561,6 +564,9 @@
<!-- Keyboard shortcuts helper -->
<dimen name="ksh_layout_width">@dimen/match_parent</dimen>
+ <dimen name="ksh_item_text_size">14sp</dimen>
+ <dimen name="ksh_item_padding">4dp</dimen>
+ <dimen name="ksh_item_margin_start">4dp</dimen>
<!-- Recents Layout -->
diff --git a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
new file mode 100644
index 0000000..c2bc53e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+public class ResizingSpace extends View {
+
+ private final int mWidth;
+ private final int mHeight;
+
+ public ResizingSpace(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (getVisibility() == VISIBLE) {
+ setVisibility(INVISIBLE);
+ }
+ TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.ViewGroup_Layout);
+ mWidth = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_width, 0);
+ mHeight = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_height, 0);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ LayoutParams params = getLayoutParams();
+ boolean changed = false;
+ if (mWidth > 0) {
+ int width = getContext().getResources().getDimensionPixelOffset(mWidth);
+ if (width != params.width) {
+ params.width = width;
+ changed = true;
+ }
+ }
+ if (mHeight > 0) {
+ int height = getContext().getResources().getDimensionPixelOffset(mHeight);
+ if (height != params.height) {
+ params.height = height;
+ changed = true;
+ }
+ }
+ if (changed) {
+ setLayoutParams(params);
+ }
+ }
+
+ /**
+ * Draw nothing.
+ *
+ * @param canvas an unused parameter.
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ /**
+ * Compare to: {@link View#getDefaultSize(int, int)}
+ * If mode is AT_MOST, return the child size instead of the parent size
+ * (unless it is too big).
+ */
+ private static int getDefaultSize2(int size, int measureSpec) {
+ int result = size;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ result = size;
+ break;
+ case MeasureSpec.AT_MOST:
+ result = Math.min(size, specSize);
+ break;
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(
+ getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
+ getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index c0a565db..e838191 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -329,7 +329,7 @@
*/
public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
- useAccelerateInterpolator, 0 /* fixedDuration */);
+ useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
}
/**
@@ -341,17 +341,22 @@
* @param fixedDuration If not 0, this exact duration will be taken
*/
public void dismissChild(final View animView, float velocity, final Runnable endAction,
- long delay, boolean useAccelerateInterpolator, long fixedDuration) {
+ long delay, boolean useAccelerateInterpolator, long fixedDuration,
+ boolean isDismissAll) {
final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
float newPos;
boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- if (velocity < 0
- || (velocity == 0 && getTranslation(animView) < 0)
- // if we use the Menu to dismiss an item in landscape, animate up
- || (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)
- // if the language is rtl we prefer swiping to the left
- || (velocity == 0 && getTranslation(animView) == 0 && isLayoutRtl)) {
+ // if we use the Menu to dismiss an item in landscape, animate up
+ boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+ && mSwipeDirection == Y;
+ // if the language is rtl we prefer swiping to the left
+ boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+ && isLayoutRtl;
+ boolean animateLeft = velocity < 0
+ || (velocity == 0 && getTranslation(animView) < 0 && !isDismissAll);
+
+ if (animateLeft || animateLeftForRtl || animateUpForMenu) {
newPos = -getSize(animView);
} else {
newPos = getSize(animView);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index d2c60ef..84d3599 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -115,9 +115,9 @@
}
@Override // Binder interface
- public void onFinishedGoingToSleep(int reason) {
+ public void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered) {
checkPermission();
- mKeyguardViewMediator.onFinishedGoingToSleep(reason);
+ mKeyguardViewMediator.onFinishedGoingToSleep(reason, cameraGestureTriggered);
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a5dfc4b..66754a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -747,7 +747,7 @@
notifyStartedGoingToSleep();
}
- public void onFinishedGoingToSleep(int why) {
+ public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) {
if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + why + ")");
synchronized (this) {
mDeviceInteractive = false;
@@ -758,6 +758,16 @@
notifyFinishedGoingToSleep();
+ if (cameraGestureTriggered) {
+ Log.i(TAG, "Camera gesture was triggered, preventing Keyguard locking.");
+
+ // Just to make sure, make sure the device is awake.
+ mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
+ "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
+ mPendingLock = false;
+ mPendingReset = false;
+ }
+
if (mPendingReset) {
resetStateLocked();
mPendingReset = false;
@@ -771,7 +781,7 @@
// We do not have timeout and power button instant lock setting for profile lock.
// So we use the personal setting if there is any. But if there is no device
// we need to make sure we lock it immediately when the screen is off.
- if (!mLockLater) {
+ if (!mLockLater && !cameraGestureTriggered) {
doKeyguardForChildProfilesLocked();
}
@@ -794,7 +804,7 @@
// From DevicePolicyAdmin
final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
- .getMaximumTimeToLock(null, userId);
+ .getMaximumTimeToLockForUserAndProfiles(userId);
long timeout;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
new file mode 100644
index 0000000..00e6221
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import com.android.systemui.R;
+
+/**
+ * Similar to a ListView, but it will show only as many items as fit on screen and
+ * bind those instead of scrolling.
+ */
+public class AutoSizingList extends LinearLayout {
+
+ private static final String TAG = "AutoSizingList";
+ private final int mItemSize;
+ private final Handler mHandler;
+
+ private ListAdapter mAdapter;
+ private int mCount;
+
+ public AutoSizingList(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+
+ mHandler = new Handler();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoSizingList);
+ mItemSize = a.getDimensionPixelSize(R.styleable.AutoSizingList_itemHeight, 0);
+ }
+
+ public void setAdapter(ListAdapter adapter) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mDataObserver);
+ }
+ mAdapter = adapter;
+ if (adapter != null) {
+ adapter.registerDataSetObserver(mDataObserver);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (requestedHeight != 0) {
+ int count = Math.min(requestedHeight / mItemSize, getDesiredCount());
+ if (mCount != count) {
+ postRebindChildren();
+ mCount = count;
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ private int getDesiredCount() {
+ return mAdapter != null ? mAdapter.getCount() : 0;
+ }
+
+ private void postRebindChildren() {
+ mHandler.post(mBindChildren);
+ }
+
+ private void rebindChildren() {
+ if (mAdapter == null) {
+ return;
+ }
+ for (int i = 0; i < mCount; i++) {
+ View v = i < getChildCount() ? getChildAt(i) : null;
+ View newView = mAdapter.getView(i, v, this);
+ if (newView != v) {
+ if (v != null) {
+ removeView(v);
+ }
+ addView(newView, i);
+ }
+ }
+ // Ditch extra views.
+ while (getChildCount() > mCount) {
+ removeViewAt(getChildCount() - 1);
+ }
+ }
+
+ private final Runnable mBindChildren = new Runnable() {
+ @Override
+ public void run() {
+ rebindChildren();
+ }
+ };
+
+ private final DataSetObserver mDataObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ if (mCount > getDesiredCount()) {
+ mCount = getDesiredCount();
+ }
+ postRebindChildren();
+ }
+
+ @Override
+ public void onInvalidated() {
+ postRebindChildren();
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 115c9d0..5a23610 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,6 +1,8 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
@@ -199,11 +201,22 @@
@Override
public boolean updateResources() {
- if (super.updateResources()) {
- mMaxRows = mColumns != 3 ? 2 : 3;
- return true;
+ final int rows = getRows();
+ boolean changed = rows != mMaxRows;
+ if (changed) {
+ mMaxRows = rows;
+ requestLayout();
}
- return false;
+ return super.updateResources() || changed;
+ }
+
+ private int getRows() {
+ final Resources res = getContext().getResources();
+ if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // Always have 3 rows in portrait.
+ return 3;
+ }
+ return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows));
}
public void setMaxRows(int maxRows) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index 5d06aeb..0af5fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -100,7 +100,13 @@
// Since we control our own bottom, be whatever size we want.
// Otherwise the QSPanel ends up with 0 height when the window is only the
// size of the status bar.
- super.onMeasure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+ mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
+ int width = mQSPanel.getMeasuredWidth();
+ int height = ((LayoutParams) mQSPanel.getLayoutParams()).topMargin
+ + mQSPanel.getMeasuredHeight();
+ super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
// QSCustomizer is always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QSContainer.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index 25b9105..2dd4a0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -28,11 +28,10 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
@@ -45,16 +44,17 @@
private final Context mContext;
private final H mHandler = new H();
+ private final Adapter mAdapter = new Adapter();
private String mTag;
private Callback mCallback;
private boolean mItemsVisible = true;
- private LinearLayout mItems;
+ private AutoSizingList mItemList;
private View mEmpty;
- private View mMinHeightSpacer;
private TextView mEmptyText;
private ImageView mEmptyIcon;
- private int mMaxItems;
+
+ private Item[] mItems;
public QSDetailItems(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -73,27 +73,22 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mItems = (LinearLayout) findViewById(android.R.id.list);
- mItems.setVisibility(GONE);
+ mItemList = (AutoSizingList) findViewById(android.R.id.list);
+ mItemList.setVisibility(GONE);
+ mItemList.setAdapter(mAdapter);
mEmpty = findViewById(android.R.id.empty);
mEmpty.setVisibility(GONE);
mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
- mMinHeightSpacer = findViewById(R.id.min_height_spacer);
-
- // By default, a detail item view has fixed size.
- mMaxItems = getResources().getInteger(
- R.integer.quick_settings_detail_max_item_count);
- setMinHeightInItems(mMaxItems);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size);
- int count = mItems.getChildCount();
+ int count = mItemList.getChildCount();
for (int i = 0; i < count; i++) {
- View item = mItems.getChildAt(i);
+ View item = mItemList.getChildAt(i);
FontSizeUtils.updateFontSize(item, android.R.id.title,
R.dimen.qs_detail_item_primary_text_size);
FontSizeUtils.updateFontSize(item, android.R.id.summary,
@@ -110,16 +105,6 @@
mEmptyText.setText(text);
}
- /**
- * Set the minimum height of this detail view, in item count.
- */
- public void setMinHeightInItems(int minHeightInItems) {
- ViewGroup.LayoutParams lp = mMinHeightSpacer.getLayoutParams();
- lp.height = minHeightInItems * getResources().getDimensionPixelSize(
- R.dimen.qs_detail_item_height);
- mMinHeightSpacer.setLayoutParams(lp);
- }
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -153,65 +138,82 @@
}
private void handleSetItems(Item[] items) {
- final int itemCount = items != null ? Math.min(items.length, mMaxItems) : 0;
+ final int itemCount = items != null ? items.length : 0;
mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
- mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
- for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
- mItems.removeViewAt(i);
- }
- for (int i = 0; i < itemCount; i++) {
- bind(items[i], mItems.getChildAt(i));
- }
+ mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE);
+ mItems = items;
+ mAdapter.notifyDataSetChanged();
}
private void handleSetItemsVisible(boolean visible) {
if (mItemsVisible == visible) return;
mItemsVisible = visible;
- for (int i = 0; i < mItems.getChildCount(); i++) {
- mItems.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ for (int i = 0; i < mItemList.getChildCount(); i++) {
+ mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
}
}
- private void bind(final Item item, View view) {
- if (view == null) {
- view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, this, false);
- mItems.addView(view);
+ private class Adapter extends BaseAdapter {
+
+ @Override
+ public int getCount() {
+ return mItems != null ? mItems.length : 0;
}
- view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
- final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
- iv.setImageResource(item.icon);
- iv.getOverlay().clear();
- if (item.overlay != null) {
- item.overlay.setBounds(0, 0, item.overlay.getIntrinsicWidth(),
- item.overlay.getIntrinsicHeight());
- iv.getOverlay().add(item.overlay);
+
+ @Override
+ public Object getItem(int position) {
+ return mItems[position];
}
- final TextView title = (TextView) view.findViewById(android.R.id.title);
- title.setText(item.line1);
- final TextView summary = (TextView) view.findViewById(android.R.id.summary);
- final boolean twoLines = !TextUtils.isEmpty(item.line2);
- title.setMaxLines(twoLines ? 1 : 2);
- summary.setVisibility(twoLines ? VISIBLE : GONE);
- summary.setText(twoLines ? item.line2 : null);
- view.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mCallback != null) {
- mCallback.onDetailItemClick(item);
- }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ final Item item = mItems[position];
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, parent,
+ false);
}
- });
- final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
- disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
- disconnect.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mCallback != null) {
- mCallback.onDetailItemDisconnect(item);
- }
+ view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+ iv.setImageResource(item.icon);
+ iv.getOverlay().clear();
+ if (item.overlay != null) {
+ item.overlay.setBounds(0, 0, item.overlay.getIntrinsicWidth(),
+ item.overlay.getIntrinsicHeight());
+ iv.getOverlay().add(item.overlay);
}
- });
- }
+ final TextView title = (TextView) view.findViewById(android.R.id.title);
+ title.setText(item.line1);
+ final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ title.setMaxLines(twoLines ? 1 : 2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.line2 : null);
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemClick(item);
+ }
+ }
+ });
+ final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
+ disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
+ disconnect.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemDisconnect(item);
+ }
+ }
+ });
+ return view;
+ }
+ };
private class H extends Handler {
private static final int SET_ITEMS = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 55eda98..6969e25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -24,6 +24,7 @@
private int mCellMargin;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ private int mCellMarginTop;
public TileLayout(Context context) {
this(context, null);
@@ -60,9 +61,10 @@
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
mCellMargin = res.getDimensionPixelSize(R.dimen.qs_tile_margin);
+ mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
if (mColumns != columns) {
mColumns = columns;
- postInvalidate();
+ requestLayout();
return true;
}
return false;
@@ -81,7 +83,8 @@
record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
- setMeasuredDimension(width, (mCellHeight + mCellMargin) * rows);
+ setMeasuredDimension(width,
+ (mCellHeight + mCellMargin) * rows + (mCellMarginTop - mCellMargin));
}
private static int exactly(int size) {
@@ -114,7 +117,7 @@
}
private int getRowTop(int row) {
- return row * (mCellHeight + mCellMargin) + mCellMargin;
+ return row * (mCellHeight + mCellMargin) + mCellMarginTop;
}
private int getColumnStart(int column) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 1fef8f1..63c85db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -216,7 +216,6 @@
mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
R.string.quick_settings_bluetooth_detail_empty_text);
mItems.setCallback(this);
- mItems.setMinHeightInItems(0);
updateItems();
setItemsVisible(mState.value);
return mItems;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 8060e07..b1d9555 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -78,6 +78,9 @@
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.statusbar.BaseStatusBar;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* The main Recents activity that is started from AlternateRecentsComponent.
*/
@@ -733,4 +736,20 @@
});
return true;
}
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ String id = Integer.toHexString(System.identityHashCode(this));
+
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
+ writer.print(" [0x"); writer.print(id); writer.print("]");
+ writer.println();
+
+ if (mRecentsView != null) {
+ mRecentsView.dump(prefix, writer);
+ }
+ EventBus.getDefault().dump(prefix, writer);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index 0d56ae9..38ad1c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -30,6 +30,7 @@
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -652,19 +653,43 @@
/**
* @return a dump of the current state of the EventBus
*/
- public String dump() {
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(dumpInternal(prefix));
+ }
+
+ public String dumpInternal(String prefix) {
+ String innerPrefix = prefix + " ";
+ String innerInnerPrefix = innerPrefix + " ";
StringBuilder output = new StringBuilder();
+ output.append(prefix);
output.append("Registered class types:");
output.append("\n");
- for (Class<?> clz : mSubscriberTypeMap.keySet()) {
- output.append("\t");
+ ArrayList<Class<?>> subsciberTypes = new ArrayList<>(mSubscriberTypeMap.keySet());
+ Collections.sort(subsciberTypes, new Comparator<Class<?>>() {
+ @Override
+ public int compare(Class<?> o1, Class<?> o2) {
+ return o1.getSimpleName().compareTo(o2.getSimpleName());
+ }
+ });
+ for (int i = 0; i < subsciberTypes.size(); i++) {
+ Class<?> clz = subsciberTypes.get(i);
+ output.append(innerPrefix);
output.append(clz.getSimpleName());
output.append("\n");
}
+ output.append(prefix);
output.append("Event map:");
output.append("\n");
- for (Class<?> clz : mEventTypeMap.keySet()) {
- output.append("\t");
+ ArrayList<Class<?>> classes = new ArrayList<>(mEventTypeMap.keySet());
+ Collections.sort(classes, new Comparator<Class<?>>() {
+ @Override
+ public int compare(Class<?> o1, Class<?> o2) {
+ return o1.getSimpleName().compareTo(o2.getSimpleName());
+ }
+ });
+ for (int i = 0; i < classes.size(); i++) {
+ Class<?> clz = classes.get(i);
+ output.append(innerPrefix);
output.append(clz.getSimpleName());
output.append(" -> ");
output.append("\n");
@@ -673,7 +698,7 @@
Object subscriber = handler.subscriber.getReference();
if (subscriber != null) {
String id = Integer.toHexString(System.identityHashCode(subscriber));
- output.append("\t\t");
+ output.append(innerInnerPrefix);
output.append(subscriber.getClass().getSimpleName());
output.append(" [0x" + id + ", #" + handler.priority + "]");
output.append("\n");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index e28612a..69d98af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -243,4 +243,14 @@
public static float dpToPx(Resources res, float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
}
+
+ /**
+ * Returns a lightweight dump of a rect.
+ */
+ public static String dumpRect(Rect r) {
+ if (r == null) {
+ return "N:0,0-0,0";
+ }
+ return r.left + "," + r.top + "-" + r.right + "," + r.bottom;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 6668079..24eeaf2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -29,6 +29,7 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
@@ -37,6 +38,9 @@
* A task represents the top most task in the system's task stack.
*/
public class Task {
+
+ public static final String TAG = "Task";
+
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task has been bound */
@@ -100,7 +104,8 @@
@Override
public String toString() {
- return "t" + id + ", s" + stackId + ", u" + userId;
+ return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
+ lastActiveTime;
}
private void updateHashCode() {
@@ -306,4 +311,13 @@
public String toString() {
return "[" + key.toString() + "] " + title;
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.print(prefix); writer.print(key);
+ if (affiliationTaskId != key.id) {
+ writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
+ }
+ writer.print(" "); writer.print(title);
+ writer.println();
+ }
}
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 df3f56c..fbb5987 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -55,6 +55,7 @@
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -208,6 +209,8 @@
*/
public class TaskStack {
+ private static final String TAG = "TaskStack";
+
/** Task stack callbacks */
public interface TaskStackCallbacks {
/**
@@ -725,7 +728,9 @@
/** Finds the task with the specified task id. */
public Task findTaskWithId(int taskId) {
ArrayList<Task> tasks = computeAllTasksList();
- for (Task task : tasks) {
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
if (task.key.id == taskId) {
return task;
}
@@ -880,7 +885,10 @@
ArraySet<ComponentName> existingComponents = new ArraySet<>();
ArraySet<ComponentName> removedComponents = new ArraySet<>();
ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
- for (Task.TaskKey t : taskKeys) {
+ int taskKeyCount = taskKeys.size();
+ for (int i = 0; i < taskKeyCount; i++) {
+ Task.TaskKey t = taskKeys.get(i);
+
// Skip if this doesn't apply to the current user
if (t.userId != userId) continue;
@@ -903,8 +911,10 @@
@Override
public String toString() {
String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
- for (Task t : mStackTaskList.getTasks()) {
- str += " " + t.toString() + "\n";
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ str += " " + tasks.get(i).toString() + "\n";
}
return str;
}
@@ -921,4 +931,17 @@
}
return map;
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
+ writer.println();
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ tasks.get(i).dump(innerPrefix, writer);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ef81f9e..21a43d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -74,6 +74,8 @@
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -83,6 +85,8 @@
*/
public class RecentsView extends FrameLayout {
+ private static final String TAG = "RecentsView";
+
private static final int DOCK_AREA_OVERLAY_TRANSITION_DURATION = 135;
private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
@@ -758,4 +762,22 @@
top + mStackActionButton.getMeasuredHeight());
return actionButtonRect;
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ String id = Integer.toHexString(System.identityHashCode(this));
+
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
+ writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
+ writer.print(" [0x"); writer.print(id); writer.print("]");
+ writer.println();
+
+ if (mStack != null) {
+ mStack.dump(innerPrefix, writer);
+ }
+ if (mTaskStackView != null) {
+ mTaskStackView.dump(innerPrefix, writer);
+ }
+ }
}
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 c16a9be4..b75a91e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -39,6 +39,7 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -109,6 +110,8 @@
*/
public class TaskStackLayoutAlgorithm {
+ private static final String TAG = "TaskStackLayoutAlgorithm";
+
// The distribution of view bounds alpha
// XXX: This is a hack because you can currently set the max alpha to be > 1f
public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
@@ -684,7 +687,6 @@
}
/**
- *
* Returns the current stack state.
*/
public StackState getStackState() {
@@ -1157,4 +1159,44 @@
mBackOfStackTransform.visible = true;
mFrontOfStackTransform.visible = true;
}
-}
+
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+
+ writer.print(prefix); writer.print(TAG);
+ writer.write(" numStackTasks="); writer.write(mNumStackTasks);
+ writer.println();
+
+ writer.print(innerPrefix);
+ writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
+ writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
+ writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
+ writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
+ writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
+ writer.println();
+
+ writer.print(innerPrefix);
+ writer.print("minScroll="); writer.print(mMinScrollP);
+ writer.print(" maxScroll="); writer.print(mMaxScrollP);
+ writer.print(" initialScroll="); writer.print(mInitialScrollP);
+ writer.println();
+
+ writer.print(innerPrefix);
+ writer.print("focusState="); writer.print(mFocusState);
+ writer.println();
+
+ if (mTaskIndexOverrideMap.size() > 0) {
+ for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
+ int taskId = mTaskIndexOverrideMap.keyAt(i);
+ float x = mTaskIndexMap.get(taskId);
+ float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
+
+ writer.print(innerPrefix);
+ writer.print("taskId= "); writer.print(taskId);
+ writer.print(" x= "); writer.print(x);
+ writer.print(" overrideX= "); writer.print(overrideX);
+ writer.println();
+ }
+ }
+ }
+}
\ No newline at end of file
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 5416a48..13c8403 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -90,6 +90,7 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -102,6 +103,8 @@
TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks,
ViewPool.ViewPoolConsumer<TaskView, Task> {
+ private static final String TAG = "TaskStackView";
+
private final static String KEY_SAVED_STATE_SUPER = "saved_instance_state_super";
private final static String KEY_SAVED_STATE_LAYOUT_FOCUSED_STATE =
"saved_instance_state_layout_focused_state";
@@ -2067,4 +2070,37 @@
mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
Settings.System.LOCK_TO_APP_ENABLED) != 0;
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ String id = Integer.toHexString(System.identityHashCode(this));
+
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" hasDefRelayout=");
+ writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N");
+ writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N");
+ writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
+ writer.print(" initialState="); writer.print(mInitialState);
+ writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N");
+ writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N");
+ writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N");
+ writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N");
+ writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size());
+ writer.print(" numViewPool="); writer.print(mViewPool.getViews().size());
+ writer.print(" stableStackBounds="); writer.print(Utilities.dumpRect(mStableStackBounds));
+ writer.print(" stackBounds="); writer.print(Utilities.dumpRect(mStackBounds));
+ writer.print(" stableWindow="); writer.print(Utilities.dumpRect(mStableWindowRect));
+ writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect));
+ writer.print(" [0x"); writer.print(id); writer.print("]");
+ writer.println();
+
+ if (mFocusedTask != null) {
+ writer.print(innerPrefix);
+ writer.print("Focused task: ");
+ mFocusedTask.dump(innerPrefix, writer);
+ }
+
+ mLayoutAlgorithm.dump(innerPrefix, writer);
+ mStackScroller.dump(innerPrefix, writer);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 583fb88..19b3c94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -30,6 +30,8 @@
import com.android.systemui.R;
import com.android.systemui.recents.misc.Utilities;
+import java.io.PrintWriter;
+
/* The scrolling logic for a TaskStackView */
public class TaskStackViewScroller {
@@ -246,4 +248,10 @@
mScroller.abortAnimation();
}
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" stackScroll:"); writer.print(mStackScrollP);
+ writer.println();
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index ddd3ea1..d294c80 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -34,8 +34,6 @@
* Controls the docked stack divider.
*/
public class Divider extends SystemUI {
- private static final String TAG = "Divider";
- private int mDividerWindowWidth;
private DividerWindowManager mWindowManager;
private DividerView mView;
private DockDividerVisibilityListener mDockDividerVisibilityListener;
@@ -46,8 +44,6 @@
@Override
public void start() {
mWindowManager = new DividerWindowManager(mContext);
- mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
update(mContext.getResources().getConfiguration());
putComponent(Divider.class, this);
mDockDividerVisibilityListener = new DockDividerVisibilityListener();
@@ -70,9 +66,11 @@
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
+ final int size = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
- final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
- final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
+ final int width = landscape ? size : MATCH_PARENT;
+ final int height = landscape ? MATCH_PARENT : size;
mWindowManager.add(mView, width, height);
mView.setWindowManager(mWindowManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 8102fae..1b2393a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -91,6 +91,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -100,6 +101,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationGuts.OnGutsClosedListener;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -114,12 +116,12 @@
import java.util.Locale;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
-import static com.android.keyguard.KeyguardHostView.OnDismissAction;
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener {
+ ExpandableNotificationRow.OnExpandClickListener,
+ OnGutsClosedListener {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean MULTIUSER_DEBUG = false;
@@ -285,9 +287,10 @@
private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- // so we just dump our cache ...
+ // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+ // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
mUsersAllowingPrivateNotifications.clear();
+ mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
updateNotifications();
}
@@ -1010,6 +1013,7 @@
PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
+ guts.setClosedListener(this);
final String pkg = sbn.getPackageName();
String appname = pkg;
Drawable pkgicon = null;
@@ -1037,6 +1041,7 @@
settingsButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
startAppNotificationSettingsActivity(pkg, appUidF);
}
});
@@ -1047,24 +1052,43 @@
row.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- guts.saveImportance(sbn);
-
- int[] rowLocation = new int[2];
- int[] doneLocation = new int[2];
- row.getLocationOnScreen(rowLocation);
- v.getLocationOnScreen(doneLocation);
-
- final int centerX = v.getWidth() / 2;
- final int centerY = v.getHeight() / 2;
- final int x = doneLocation[0] - rowLocation[0] + centerX;
- final int y = doneLocation[1] - rowLocation[1] + centerY;
- dismissPopups(x, y);
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (guts.hasImportanceChanged() && isLockscreenPublicMode() &&
+ (mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ OnDismissAction dismissAction = new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ saveImportanceCloseControls(sbn, row, guts, v);
+ return true;
+ }
+ };
+ onLockedNotificationImportanceChange(dismissAction);
+ } else {
+ saveImportanceCloseControls(sbn, row, guts, v);
+ }
}
});
-
guts.bindImportance(pmUser, sbn, row, mNotificationData.getImportance(sbn.getKey()));
}
+ private void saveImportanceCloseControls(StatusBarNotification sbn,
+ ExpandableNotificationRow row, NotificationGuts guts, View done) {
+ guts.resetFalsingCheck();
+ guts.saveImportance(sbn);
+
+ int[] rowLocation = new int[2];
+ int[] doneLocation = new int[2];
+ row.getLocationOnScreen(rowLocation);
+ done.getLocationOnScreen(doneLocation);
+
+ final int centerX = done.getWidth() / 2;
+ final int centerY = done.getHeight() / 2;
+ final int x = doneLocation[0] - rowLocation[0] + centerX;
+ final int y = doneLocation[1] - rowLocation[1] + centerY;
+ dismissPopups(x, y);
+ }
+
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
return new SwipeHelper.LongPressListener() {
@Override
@@ -1119,7 +1143,8 @@
}
});
a.start();
- guts.setExposed(true);
+ guts.setExposed(true /* exposed */,
+ mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
row.closeRemoteInput();
mStackScroller.onHeightChanged(null, true /* needsAnimation */);
mNotificationGutsExposed = guts;
@@ -1147,31 +1172,7 @@
public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
if (mNotificationGutsExposed != null) {
- final NotificationGuts v = mNotificationGutsExposed;
- mNotificationGutsExposed = null;
-
- if (v.getWindowToken() == null) return;
- if (x == -1 || y == -1) {
- x = (v.getLeft() + v.getRight()) / 2;
- y = (v.getTop() + v.getHeight() / 2);
- }
- final double horz = Math.max(v.getWidth() - x, x);
- final double vert = Math.max(v.getHeight() - y, y);
- final float r = (float) Math.hypot(horz, vert);
- final Animator a = ViewAnimationUtils.createCircularReveal(v,
- x, y, r, 0);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- v.setVisibility(View.GONE);
- }
- });
- a.start();
- v.setExposed(false);
- mStackScroller.onHeightChanged(null, true /* needsAnimation */);
+ mNotificationGutsExposed.closeControls(x, y, true /* notify */);
}
if (resetGear) {
mStackScroller.resetExposedGearView(animate, true /* force */);
@@ -1179,6 +1180,12 @@
}
@Override
+ public void onGutsClosed(NotificationGuts guts) {
+ mStackScroller.onHeightChanged(null, true /* needsAnimation */);
+ mNotificationGutsExposed = null;
+ }
+
+ @Override
public void showRecentApps(boolean triggeredFromAltTab) {
int msg = MSG_SHOW_RECENT_APPS;
mHandler.removeMessages(msg);
@@ -1450,6 +1457,8 @@
}
}
+ protected void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {}
+
protected void onLockedRemoteInput(ExpandableNotificationRow row, View clickedView) {}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 2b365dc..977a77d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -21,6 +21,9 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,6 +40,7 @@
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager.KeyboardShortcutsReceiver;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -52,158 +56,13 @@
/**
* Contains functionality for handling keyboard shortcuts.
*/
-public class KeyboardShortcuts {
+public final class KeyboardShortcuts {
private static final String TAG = KeyboardShortcuts.class.getSimpleName();
- private static final SparseArray<String> SPECIAL_CHARACTER_NAMES = new SparseArray<>();
- private static final SparseArray<String> MODIFIER_NAMES = new SparseArray<>();
-
- private static void loadSpecialCharacterNames(Context context) {
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_PERIOD, ".");
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
- context.getString(R.string.keyboard_key_media_play_pause));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
- context.getString(R.string.keyboard_key_media_previous));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_REWIND,
- context.getString(R.string.keyboard_key_media_rewind));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
- context.getString(R.string.keyboard_key_media_fast_forward));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_A,
- context.getString(R.string.keyboard_key_button_template, "A"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_B,
- context.getString(R.string.keyboard_key_button_template, "B"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_C,
- context.getString(R.string.keyboard_key_button_template, "C"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_X,
- context.getString(R.string.keyboard_key_button_template, "X"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_Y,
- context.getString(R.string.keyboard_key_button_template, "Y"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_Z,
- context.getString(R.string.keyboard_key_button_template, "Z"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_L1,
- context.getString(R.string.keyboard_key_button_template, "L1"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_R1,
- context.getString(R.string.keyboard_key_button_template, "R1"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_L2,
- context.getString(R.string.keyboard_key_button_template, "L2"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_R2,
- context.getString(R.string.keyboard_key_button_template, "R2"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_START,
- context.getString(R.string.keyboard_key_button_template, "Start"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_SELECT,
- context.getString(R.string.keyboard_key_button_template, "Select"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BUTTON_MODE,
- context.getString(R.string.keyboard_key_button_template, "Mode"));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_BREAK, "Break");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F1, "F1");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F2, "F2");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F3, "F3");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F4, "F4");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F5, "F5");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F6, "F6");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F7, "F7");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F8, "F8");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F9, "F9");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F10, "F10");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F11, "F11");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_F12, "F12");
- SPECIAL_CHARACTER_NAMES.put(
- KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_0,
- context.getString(R.string.keyboard_key_numpad_template, "0"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_1,
- context.getString(R.string.keyboard_key_numpad_template, "1"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_2,
- context.getString(R.string.keyboard_key_numpad_template, "2"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_3,
- context.getString(R.string.keyboard_key_numpad_template, "3"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_4,
- context.getString(R.string.keyboard_key_numpad_template, "4"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_5,
- context.getString(R.string.keyboard_key_numpad_template, "5"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_6,
- context.getString(R.string.keyboard_key_numpad_template, "6"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_7,
- context.getString(R.string.keyboard_key_numpad_template, "7"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_8,
- context.getString(R.string.keyboard_key_numpad_template, "8"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_9,
- context.getString(R.string.keyboard_key_numpad_template, "9"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
- context.getString(R.string.keyboard_key_numpad_template, "/"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
- context.getString(R.string.keyboard_key_numpad_template, "*"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
- context.getString(R.string.keyboard_key_numpad_template, "-"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_ADD,
- context.getString(R.string.keyboard_key_numpad_template, "+"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_DOT,
- context.getString(R.string.keyboard_key_numpad_template, "."));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
- context.getString(R.string.keyboard_key_numpad_template, ","));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
- context.getString(R.string.keyboard_key_numpad_template,
- context.getString(R.string.keyboard_key_enter)));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
- context.getString(R.string.keyboard_key_numpad_template, "="));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
- context.getString(R.string.keyboard_key_numpad_template, "("));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
- context.getString(R.string.keyboard_key_numpad_template, ")"));
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_EISU, "英数");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_HENKAN, "変換");
- SPECIAL_CHARACTER_NAMES.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
-
- MODIFIER_NAMES.put(KeyEvent.META_META_ON, "Meta");
- MODIFIER_NAMES.put(KeyEvent.META_CTRL_ON, "Ctrl");
- MODIFIER_NAMES.put(KeyEvent.META_ALT_ON, "Alt");
- MODIFIER_NAMES.put(KeyEvent.META_SHIFT_ON, "Shift");
- MODIFIER_NAMES.put(KeyEvent.META_SYM_ON, "Sym");
- MODIFIER_NAMES.put(KeyEvent.META_FUNCTION_ON, "Fn");
- }
+ private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
+ private final SparseArray<String> mModifierNames = new SparseArray<>();
+ private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
+ private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
@@ -218,9 +77,170 @@
public KeyboardShortcuts(Context context) {
this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_Material_Light);
- if (SPECIAL_CHARACTER_NAMES.size() == 0) {
- loadSpecialCharacterNames(context);
- }
+ loadResources(context);
+ }
+
+ private void loadResources(Context context) {
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+ context.getString(R.string.keyboard_key_media_play_pause));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
+ context.getString(R.string.keyboard_key_media_previous));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
+ context.getString(R.string.keyboard_key_media_rewind));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
+ context.getString(R.string.keyboard_key_media_fast_forward));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
+ context.getString(R.string.keyboard_key_button_template, "A"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
+ context.getString(R.string.keyboard_key_button_template, "B"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
+ context.getString(R.string.keyboard_key_button_template, "C"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
+ context.getString(R.string.keyboard_key_button_template, "X"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
+ context.getString(R.string.keyboard_key_button_template, "Y"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
+ context.getString(R.string.keyboard_key_button_template, "Z"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
+ context.getString(R.string.keyboard_key_button_template, "L1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
+ context.getString(R.string.keyboard_key_button_template, "R1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
+ context.getString(R.string.keyboard_key_button_template, "L2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
+ context.getString(R.string.keyboard_key_button_template, "R2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
+ context.getString(R.string.keyboard_key_button_template, "Start"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
+ context.getString(R.string.keyboard_key_button_template, "Select"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
+ context.getString(R.string.keyboard_key_button_template, "Mode"));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
+ mSpecialCharacterNames.put(
+ KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
+ context.getString(R.string.keyboard_key_numpad_template, "0"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
+ context.getString(R.string.keyboard_key_numpad_template, "1"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
+ context.getString(R.string.keyboard_key_numpad_template, "2"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
+ context.getString(R.string.keyboard_key_numpad_template, "3"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
+ context.getString(R.string.keyboard_key_numpad_template, "4"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
+ context.getString(R.string.keyboard_key_numpad_template, "5"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
+ context.getString(R.string.keyboard_key_numpad_template, "6"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
+ context.getString(R.string.keyboard_key_numpad_template, "7"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
+ context.getString(R.string.keyboard_key_numpad_template, "8"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
+ context.getString(R.string.keyboard_key_numpad_template, "9"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
+ context.getString(R.string.keyboard_key_numpad_template, "/"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
+ context.getString(R.string.keyboard_key_numpad_template, "*"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
+ context.getString(R.string.keyboard_key_numpad_template, "-"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
+ context.getString(R.string.keyboard_key_numpad_template, "+"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
+ context.getString(R.string.keyboard_key_numpad_template, "."));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
+ context.getString(R.string.keyboard_key_numpad_template, ","));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
+ context.getString(R.string.keyboard_key_numpad_template,
+ context.getString(R.string.keyboard_key_enter)));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
+ context.getString(R.string.keyboard_key_numpad_template, "="));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
+ context.getString(R.string.keyboard_key_numpad_template, "("));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
+ context.getString(R.string.keyboard_key_numpad_template, ")"));
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+
+ mModifierNames.put(KeyEvent.META_META_ON, "Meta");
+ mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
+ mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
+ mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
+ mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
+ mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
+
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
+ mSpecialCharacterDrawables.put(
+ KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
+
+ mModifierDrawables.put(
+ KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
}
public void toggleKeyboardShortcuts(int deviceId) {
@@ -343,6 +363,8 @@
List<KeyboardShortcutGroup> keyboardShortcutGroups) {
LayoutInflater inflater = LayoutInflater.from(mContext);
final int keyboardShortcutGroupsSize = keyboardShortcutGroups.size();
+ // Needed to be able to scale the image items to the same height as the text items.
+ final int shortcutTextItemHeight = getShortcutTextItemHeight(inflater);
for (int i = 0; i < keyboardShortcutGroupsSize; i++) {
KeyboardShortcutGroup group = keyboardShortcutGroups.get(i);
TextView categoryTitle = (TextView) inflater.inflate(
@@ -364,7 +386,7 @@
Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
continue;
}
- List<String> shortcutKeys = getHumanReadableShortcutKeys(info);
+ List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
if (shortcutKeys == null) {
// Ignore shortcuts we can't display keys for.
Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
@@ -380,11 +402,26 @@
.findViewById(R.id.keyboard_shortcuts_item_container);
final int shortcutKeysSize = shortcutKeys.size();
for (int k = 0; k < shortcutKeysSize; k++) {
- String shortcutKey = shortcutKeys.get(k);
- TextView shortcutKeyView = (TextView) inflater.inflate(
- R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer, false);
- shortcutKeyView.setText(shortcutKey);
- shortcutItemsContainer.addView(shortcutKeyView);
+ StringOrDrawable shortcutRepresentation = shortcutKeys.get(k);
+ if (shortcutRepresentation.drawable != null) {
+ ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
+ false);
+ Bitmap bitmap = Bitmap.createBitmap(shortcutTextItemHeight,
+ shortcutTextItemHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ shortcutRepresentation.drawable.setBounds(0, 0, canvas.getWidth(),
+ canvas.getHeight());
+ shortcutRepresentation.drawable.draw(canvas);
+ shortcutKeyIconView.setImageBitmap(bitmap);
+ shortcutItemsContainer.addView(shortcutKeyIconView);
+ } else if (shortcutRepresentation.string != null) {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer,
+ false);
+ shortcutKeyTextView.setText(shortcutRepresentation.string);
+ shortcutItemsContainer.addView(shortcutKeyTextView);
+ }
}
shortcutContainer.addView(shortcutView);
}
@@ -398,16 +435,29 @@
}
}
- private List<String> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
- List<String> shortcutKeys = getHumanReadableModifiers(info);
+ private int getShortcutTextItemHeight(LayoutInflater inflater) {
+ TextView shortcutKeyTextView = (TextView) inflater.inflate(
+ R.layout.keyboard_shortcuts_key_view, null, false);
+ shortcutKeyTextView.measure(
+ View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return shortcutKeyTextView.getMeasuredHeight()
+ - shortcutKeyTextView.getPaddingTop()
+ - shortcutKeyTextView.getPaddingBottom();
+ }
+
+ private List<StringOrDrawable> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
+ List<StringOrDrawable> shortcutKeys = getHumanReadableModifiers(info);
if (shortcutKeys == null) {
return null;
}
- String displayLabelString;
+ String displayLabelString = null;
+ Drawable displayLabelDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
displayLabelString = String.valueOf(info.getBaseCharacter());
- } else if (SPECIAL_CHARACTER_NAMES.get(info.getKeycode()) != null) {
- displayLabelString = SPECIAL_CHARACTER_NAMES.get(info.getKeycode());
+ } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
+ displayLabelDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
+ } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
+ displayLabelString = mSpecialCharacterNames.get(info.getKeycode());
} else {
// Special case for shortcuts with no base key or keycode.
if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
@@ -422,20 +472,31 @@
return null;
}
}
- shortcutKeys.add(displayLabelString.toUpperCase());
+
+ if (displayLabelDrawable != null) {
+ shortcutKeys.add(new StringOrDrawable(displayLabelDrawable));
+ } else if (displayLabelString != null) {
+ shortcutKeys.add(new StringOrDrawable(displayLabelString.toUpperCase()));
+ }
return shortcutKeys;
}
- private List<String> getHumanReadableModifiers(KeyboardShortcutInfo info) {
- final List<String> shortcutKeys = new ArrayList<>();
+ private List<StringOrDrawable> getHumanReadableModifiers(KeyboardShortcutInfo info) {
+ final List<StringOrDrawable> shortcutKeys = new ArrayList<>();
int modifiers = info.getModifiers();
if (modifiers == 0) {
return shortcutKeys;
}
- for(int i = 0; i < MODIFIER_NAMES.size(); ++i) {
- final int supportedModifier = MODIFIER_NAMES.keyAt(i);
+ for(int i = 0; i < mModifierNames.size(); ++i) {
+ final int supportedModifier = mModifierNames.keyAt(i);
if ((modifiers & supportedModifier) != 0) {
- shortcutKeys.add(MODIFIER_NAMES.get(supportedModifier).toUpperCase());
+ if (mModifierDrawables.get(supportedModifier) != null) {
+ shortcutKeys.add(new StringOrDrawable(
+ mModifierDrawables.get(supportedModifier)));
+ } else {
+ shortcutKeys.add(new StringOrDrawable(
+ mModifierNames.get(supportedModifier).toUpperCase()));
+ }
modifiers &= ~supportedModifier;
}
}
@@ -445,4 +506,17 @@
}
return shortcutKeys;
}
+
+ private static final class StringOrDrawable {
+ public String string;
+ public Drawable drawable;
+
+ public StringOrDrawable(String string) {
+ this.string = string;
+ }
+
+ public StringOrDrawable(Drawable drawable) {
+ this.drawable = drawable;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 45a24a0..3c464d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -16,28 +16,35 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.INotificationManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewAnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
+import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.tuner.TunerService;
/**
@@ -46,6 +53,8 @@
public class NotificationGuts extends LinearLayout implements TunerService.Tunable {
public static final String SHOW_SLIDER = "show_importance_slider";
+ private static final long CLOSE_GUTS_DELAY = 8000;
+
private Drawable mBackground;
private int mClipTopAmount;
private int mActualHeight;
@@ -59,10 +68,35 @@
private RadioButton mSilent;
private RadioButton mReset;
+ private Handler mHandler;
+ private Runnable mFalsingCheck;
+ private boolean mNeedsFalsingProtection;
+ private OnGutsClosedListener mListener;
+
+ public interface OnGutsClosedListener {
+ public void onGutsClosed(NotificationGuts guts);
+ }
+
public NotificationGuts(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
TunerService.get(mContext).addTunable(this, SHOW_SLIDER);
+ mHandler = new Handler();
+ mFalsingCheck = new Runnable() {
+ @Override
+ public void run() {
+ if (mNeedsFalsingProtection && mExposed) {
+ closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+ }
+ }
+ };
+ }
+
+ public void resetFalsingCheck() {
+ mHandler.removeCallbacks(mFalsingCheck);
+ if (mNeedsFalsingProtection && mExposed) {
+ mHandler.postDelayed(mFalsingCheck, CLOSE_GUTS_DELAY);
+ }
}
@Override
@@ -130,30 +164,23 @@
importanceSlider.setVisibility(View.VISIBLE);
importanceButtons.setVisibility(View.GONE);
} else {
- int userImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+ mStartingImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
try {
- userImportance =
+ mStartingImportance =
mINotificationManager.getImportance(sbn.getPackageName(), sbn.getUid());
} catch (RemoteException e) {}
- bindToggles(importanceButtons, userImportance, systemApp);
+ bindToggles(importanceButtons, mStartingImportance, systemApp);
importanceButtons.setVisibility(View.VISIBLE);
importanceSlider.setVisibility(View.GONE);
}
}
+ public boolean hasImportanceChanged() {
+ return mStartingImportance != getSelectedImportance();
+ }
+
void saveImportance(final StatusBarNotification sbn) {
- int progress;
- if (mSeekBar!= null && mSeekBar.isShown()) {
- progress = mSeekBar.getProgress();
- } else {
- if (mBlock.isChecked()) {
- progress = NotificationListenerService.Ranking.IMPORTANCE_NONE;
- } else if (mSilent.isChecked()) {
- progress = NotificationListenerService.Ranking.IMPORTANCE_LOW;
- } else {
- progress = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
- }
- }
+ int progress = getSelectedImportance();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
progress - mStartingImportance);
try {
@@ -163,8 +190,29 @@
}
}
+ private int getSelectedImportance() {
+ if (mSeekBar!= null && mSeekBar.isShown()) {
+ return mSeekBar.getProgress();
+ } else {
+ if (mBlock.isChecked()) {
+ return NotificationListenerService.Ranking.IMPORTANCE_NONE;
+ } else if (mSilent.isChecked()) {
+ return NotificationListenerService.Ranking.IMPORTANCE_LOW;
+ } else {
+ return NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+ }
+ }
+ }
+
private void bindToggles(final View importanceButtons, final int importance,
final boolean systemApp) {
+ ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+ new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ resetFalsingCheck();
+ }
+ });
mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
@@ -198,6 +246,7 @@
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ resetFalsingCheck();
if (progress < minProgress) {
seekBar.setProgress(minProgress);
progress = minProgress;
@@ -210,7 +259,7 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
- // no-op
+ resetFalsingCheck();
}
@Override
@@ -256,6 +305,38 @@
mSeekBar.setProgress(mStartingImportance);
}
+ public void closeControls(int x, int y, boolean notify) {
+ if (getWindowToken() == null) {
+ if (notify && mListener != null) {
+ mListener.onGutsClosed(this);
+ }
+ return;
+ }
+ if (x == -1 || y == -1) {
+ x = (getLeft() + getRight()) / 2;
+ y = (getTop() + getHeight() / 2);
+ }
+ final double horz = Math.max(getWidth() - x, x);
+ final double vert = Math.max(getHeight() - y, y);
+ final float r = (float) Math.hypot(horz, vert);
+ final Animator a = ViewAnimationUtils.createCircularReveal(this,
+ x, y, r, 0);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ setVisibility(View.GONE);
+ }
+ });
+ a.start();
+ setExposed(false, mNeedsFalsingProtection);
+ if (notify && mListener != null) {
+ mListener.onGutsClosed(this);
+ }
+ }
+
public void setActualHeight(int actualHeight) {
mActualHeight = actualHeight;
invalidate();
@@ -277,8 +358,18 @@
return false;
}
- public void setExposed(boolean exposed) {
+ public void setClosedListener(OnGutsClosedListener listener) {
+ mListener = listener;
+ }
+
+ public void setExposed(boolean exposed, boolean needsFalsingProtection) {
mExposed = exposed;
+ mNeedsFalsingProtection = needsFalsingProtection;
+ if (mExposed && mNeedsFalsingProtection) {
+ resetFalsingCheck();
+ } else {
+ mHandler.removeCallbacks(mFalsingCheck);
+ }
}
public boolean areGutsExposed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index c70aad2..06d79a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -131,11 +131,8 @@
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.app_name_text));
mComparators.add(HeaderProcessor.forTextView(mRow,
- com.android.internal.R.id.header_sub_text));
- mComparators.add(HeaderProcessor.forTextView(mRow,
- com.android.internal.R.id.header_content_info));
- mDividers.add(com.android.internal.R.id.sub_text_divider);
- mDividers.add(com.android.internal.R.id.content_info_divider);
+ com.android.internal.R.id.header_text));
+ mDividers.add(com.android.internal.R.id.header_text_divider);
mDividers.add(com.android.internal.R.id.time_divider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 260c969..ec45d60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -182,7 +182,7 @@
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
for (int i = 0; i < buttons.length; i++) {
- inflateButton(buttons[i], parent, landscape);
+ inflateButton(buttons[i], parent, landscape, i);
}
}
@@ -195,7 +195,8 @@
}
@Nullable
- protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape) {
+ protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
+ int indexInParent) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
float size = extractSize(buttonSpec);
String button = extractButton(buttonSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 56a7dbe..6859348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -76,6 +76,8 @@
private Drawable mHomeDefaultIcon, mHomeCarModeIcon;
private Drawable mRecentIcon;
private Drawable mDockedIcon;
+ private Drawable mImeIcon;
+ private Drawable mMenuIcon;
private NavigationBarGestureHelper mGestureHelper;
private DeadZone mDeadZone;
@@ -270,7 +272,8 @@
}
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
- if (oldConfig.orientation != newConfig.orientation) {
+ if (oldConfig.orientation != newConfig.orientation
+ || oldConfig.densityDpi != newConfig.densityDpi) {
mDockedIcon = ctx.getDrawable(R.drawable.ic_sysbar_docked);
}
if (oldConfig.densityDpi != newConfig.densityDpi) {
@@ -280,8 +283,10 @@
mBackAltLandIcon = mBackAltIcon;
mHomeDefaultIcon = ctx.getDrawable(R.drawable.ic_sysbar_home);
-
mRecentIcon = ctx.getDrawable(R.drawable.ic_sysbar_recent);
+ mMenuIcon = ctx.getDrawable(R.drawable.ic_sysbar_menu);
+ mImeIcon = ctx.getDrawable(R.drawable.ic_ime_switcher_default);
+
updateCarModeIcons(ctx);
}
}
@@ -348,9 +353,11 @@
final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
+ getImeSwitchButton().setImageDrawable(mImeIcon);
// Update menu button in case the IME state has changed.
setMenuVisibility(mShowMenu, true);
+ getMenuButton().setImageDrawable(mMenuIcon);
setDisabledFlags(mDisabledFlags, true);
}
@@ -595,14 +602,12 @@
super.onConfigurationChanged(newConfig);
boolean uiCarModeChanged = updateCarMode(newConfig);
updateTaskSwitchHelper();
- if (uiCarModeChanged) {
- // uiMode changed either from carmode or to carmode.
- // replace the nav bar button icons based on which mode
- // we are switching to.
- setNavigationIconHints(mNavigationIconHints, true);
- }
updateIcons(getContext(), mConfiguration, newConfig);
updateRecentsIcon();
+ if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi) {
+ // If car mode or density changes, we need to reset the icons.
+ setNavigationIconHints(mNavigationIconHints, true);
+ }
mConfiguration.updateFrom(newConfig);
}
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 fce893e..bf58592 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3303,10 +3303,14 @@
protected void loadDimens() {
final Resources res = mContext.getResources();
+ int oldBarHeight = mNaturalBarHeight;
mNaturalBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
-
- mMaxAllowedKeyguardNotifications = res.getInteger(R.integer.keyguard_max_notification_count);
+ if (mStatusBarWindowManager != null && mNaturalBarHeight != oldBarHeight) {
+ mStatusBarWindowManager.setBarHeight(mNaturalBarHeight);
+ }
+ mMaxAllowedKeyguardNotifications = res.getInteger(
+ R.integer.keyguard_max_notification_count);
if (DEBUG) Log.v(TAG, "updateResources");
}
@@ -4193,6 +4197,12 @@
}
@Override
+ public void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
+ mLeaveOpenOnKeyguardHide = true;
+ dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
+ }
+
+ @Override
protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
mLeaveOpenOnKeyguardHide = true;
showBouncer();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index f3aba4f..e8170fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -309,7 +309,7 @@
public void setupHost(final QSTileHost host) {
mHost = host;
- host.setHeaderView(this);
+ host.setHeaderView(mExpandIndicator);
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
setUserInfoController(host.getUserInfoController());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index b271380..888e19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -323,6 +323,11 @@
apply(mCurrentState);
}
+ public void setBarHeight(int barHeight) {
+ mBarHeight = barHeight;
+ apply(mCurrentState);
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("StatusBarWindowManager state:");
pw.println(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 46a49ee..0e3ec19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -918,7 +918,7 @@
int positionInLinearLayout = getPositionInLinearLayout(v);
int targetScroll = positionInLinearLayout + expandableView.getActualHeight() +
- mBottomInset - getHeight() + getTopPadding();
+ getImeInset() - getHeight() + getTopPadding();
if (mOwnScrollY < targetScroll) {
mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
mDontReportNextOverScroll = true;
@@ -928,8 +928,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = Math.max(0, insets.getSystemWindowInsetBottom()
- - (getRootView().getHeight() - getHeight()));
+ mBottomInset = insets.getSystemWindowInsetBottom();
int range = getScrollRange();
if (mOwnScrollY > range) {
@@ -987,7 +986,8 @@
}
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
- mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
+ mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
+ true /* isDismissAll */);
}
public void snapViewIfNeeded(View child) {
@@ -1498,23 +1498,17 @@
}
private int getScrollRange() {
- int scrollRange = 0;
- ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
- if (firstChild != null) {
- int contentHeight = getContentHeight();
- scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
- + mBottomStackSlowDownHeight);
- if (scrollRange > 0) {
- int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
- // We want to at least be able collapse the first item and not ending in a weird
- // end state.
- scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
- - firstChild.getMinHeight());
- }
- }
- int imeOverlap = Math.max(0,
- getContentHeight() - (getHeight() - mBottomInset));
- return scrollRange + imeOverlap;
+ int contentHeight = getContentHeight();
+ int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
+ + mBottomStackSlowDownHeight);
+ int imeInset = getImeInset();
+ scrollRange += Math.min(imeInset, Math.max(0,
+ getContentHeight() - (getHeight() - imeInset)));
+ return scrollRange;
+ }
+
+ private int getImeInset() {
+ return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
}
/**
@@ -3201,9 +3195,6 @@
disableClipOptimization();
}
handleDismissAllClipping();
- if (mCurrIconRow != null && mCurrIconRow.isVisible()) {
- mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
- }
}
private void handleDismissAllClipping() {
@@ -3463,8 +3454,10 @@
}
private class NotificationSwipeHelper extends SwipeHelper {
- private static final long GEAR_SHOW_DELAY = 60;
+ private static final long SHOW_GEAR_DELAY = 60;
+ private static final long COVER_GEAR_DELAY = 4000;
private CheckForDrag mCheckForDrag;
+ private Runnable mFalsingCheck;
private Handler mHandler;
private boolean mGearSnappedTo;
private boolean mGearSnappedOnLeft;
@@ -3472,6 +3465,12 @@
public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
super(swipeDirection, callback, context);
mHandler = new Handler();
+ mFalsingCheck = new Runnable() {
+ @Override
+ public void run() {
+ resetExposedGearView(true /* animate */, true /* force */);
+ }
+ };
}
@Override
@@ -3486,6 +3485,7 @@
}
mCheckForDrag = null;
mCurrIconRow = null;
+ mHandler.removeCallbacks(mFalsingCheck);
// Slide back any notifications that might be showing a gear
resetExposedGearView(true /* animate */, false /* force */);
@@ -3499,6 +3499,8 @@
@Override
public void onMoveUpdate(View view, float translation, float delta) {
+ mHandler.removeCallbacks(mFalsingCheck);
+
if (mCurrIconRow != null) {
mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
@@ -3624,6 +3626,12 @@
setSnappedToGear(true);
}
onDragCancelled(animView);
+
+ // If we're on the lockscreen we want to false this.
+ if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ mHandler.removeCallbacks(mFalsingCheck);
+ mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
+ }
super.snapChild(animView, target, velocity);
}
@@ -3631,25 +3639,24 @@
if (mTranslatingParentView == null) {
return false;
}
- final float snapBackThreshold = getSpaceForGear(animView);
+ // If the notification can't be dismissed then how far it can move is
+ // restricted -- reduce the distance it needs to move in this case.
+ final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
+ final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
final float translation = getTranslation(animView);
final boolean fromLeft = translation > 0;
final float absTrans = Math.abs(translation);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
- // If the notification can't be dismissed then how far it can move is
- // restricted -- reduce the distance it needs to move in this case.
- final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
- return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold;
+ return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
+ ? (translation > snapBackThreshold && translation <= notiThreshold)
+ : (translation < -snapBackThreshold && translation >= -notiThreshold));
}
@Override
public Animator getViewTranslationAnimator(View v, float target,
AnimatorUpdateListener listener) {
- if (mDismissAllInProgress) {
- // When dismissing all, we translate the entire view instead.
- return super.getViewTranslationAnimator(v, target, listener);
- } else if (v instanceof ExpandableNotificationRow) {
+ if (v instanceof ExpandableNotificationRow) {
return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
} else {
return super.getViewTranslationAnimator(v, target, listener);
@@ -3658,22 +3665,12 @@
@Override
public void setTranslation(View v, float translate) {
- if (mDismissAllInProgress) {
- // When dismissing all, we translate the entire view instead.
- super.setTranslation(v, translate);
- } else {
- ((ExpandableView) v).setTranslation(translate);
- }
+ ((ExpandableView) v).setTranslation(translate);
}
@Override
public float getTranslation(View v) {
- if (mDismissAllInProgress) {
- // When dismissing all, we translate the entire view instead.
- return super.getTranslation(v);
- } else {
- return ((ExpandableView) v).getTranslation();
- }
+ return ((ExpandableView) v).getTranslation();
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
@@ -3740,7 +3737,7 @@
private void checkForDrag() {
if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
mCheckForDrag = new CheckForDrag();
- mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
+ mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
}
}
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java
index a0b5c15..e98b4aa 100644
--- a/services/core/java/com/android/server/AnyMotionDetector.java
+++ b/services/core/java/com/android/server/AnyMotionDetector.java
@@ -108,63 +108,71 @@
public AnyMotionDetector(PowerManager pm, Handler handler, SensorManager sm,
DeviceIdleCallback callback, float thresholdAngle) {
if (DEBUG) Slog.d(TAG, "AnyMotionDetector instantiated.");
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mWakeLock.setReferenceCounted(false);
- mHandler = handler;
- mSensorManager = sm;
- mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- mMeasurementInProgress = false;
- mState = STATE_INACTIVE;
- mCallback = callback;
- mThresholdAngle = thresholdAngle;
- mRunningStats = new RunningSignalStats();
- mNumSufficientSamples = (int) Math.ceil(
- ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS));
- if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples);
+ synchronized (mLock) {
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setReferenceCounted(false);
+ mHandler = handler;
+ mSensorManager = sm;
+ mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMeasurementInProgress = false;
+ mState = STATE_INACTIVE;
+ mCallback = callback;
+ mThresholdAngle = thresholdAngle;
+ mRunningStats = new RunningSignalStats();
+ mNumSufficientSamples = (int) Math.ceil(
+ ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS));
+ if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples);
+ }
}
/*
* Acquire accel data until we determine AnyMotion status.
*/
public void checkForAnyMotion() {
- if (DEBUG) Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
+ if (DEBUG) {
+ Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
+ }
if (mState != STATE_ACTIVE) {
- mState = STATE_ACTIVE;
- if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
- mCurrentGravityVector = null;
- mPreviousGravityVector = null;
- startOrientationMeasurement();
+ synchronized (mLock) {
+ mState = STATE_ACTIVE;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
+ }
+ mCurrentGravityVector = null;
+ mPreviousGravityVector = null;
+ mWakeLock.acquire();
+ startOrientationMeasurementLocked();
+ }
}
}
public void stop() {
if (mState == STATE_ACTIVE) {
- mState = STATE_INACTIVE;
- if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE.");
- if (mMeasurementInProgress) {
- mMeasurementInProgress = false;
- mSensorManager.unregisterListener(mListener);
+ synchronized (mLock) {
+ mState = STATE_INACTIVE;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE.");
+ if (mMeasurementInProgress) {
+ mMeasurementInProgress = false;
+ mSensorManager.unregisterListener(mListener);
+ }
+ mHandler.removeCallbacks(mMeasurementTimeout);
+ mHandler.removeCallbacks(mSensorRestart);
+ mCurrentGravityVector = null;
+ mPreviousGravityVector = null;
+ mWakeLock.release();
}
- mHandler.removeCallbacks(mMeasurementTimeout);
- mHandler.removeCallbacks(mSensorRestart);
- mWakeLock.release();
- mCurrentGravityVector = null;
- mPreviousGravityVector = null;
}
}
- private void startOrientationMeasurement() {
- if (DEBUG) Slog.d(TAG, "startOrientationMeasurement: mMeasurementInProgress=" +
+ private void startOrientationMeasurementLocked() {
+ if (DEBUG) Slog.d(TAG, "startOrientationMeasurementLocked: mMeasurementInProgress=" +
mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null));
-
if (!mMeasurementInProgress && mAccelSensor != null) {
if (mSensorManager.registerListener(mListener, mAccelSensor,
SAMPLING_INTERVAL_MILLIS * 1000)) {
- mWakeLock.acquire();
mMeasurementInProgress = true;
mRunningStats.reset();
}
-
Message msg = Message.obtain(mHandler, mMeasurementTimeout);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
@@ -178,7 +186,6 @@
if (mMeasurementInProgress) {
mSensorManager.unregisterListener(mListener);
mHandler.removeCallbacks(mMeasurementTimeout);
- mWakeLock.release();
long detectionEndTime = SystemClock.elapsedRealtime();
mMeasurementInProgress = false;
mPreviousGravityVector = mCurrentGravityVector;
@@ -196,8 +203,10 @@
status = getStationaryStatus();
if (DEBUG) Slog.d(TAG, "getStationaryStatus() returned " + status);
if (status != RESULT_UNKNOWN) {
- if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " +
- status);
+ mWakeLock.release();
+ if (DEBUG) {
+ Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " + status);
+ }
mState = STATE_INACTIVE;
} else {
/*
@@ -275,7 +284,7 @@
@Override
public void run() {
synchronized (mLock) {
- startOrientationMeasurement();
+ startOrientationMeasurementLocked();
}
}
};
@@ -442,4 +451,4 @@
return msg;
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 82a36b4..966deb6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -991,7 +991,16 @@
@Override
public Network getActiveNetwork() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ return getActiveNetworkForUidInternal(Binder.getCallingUid());
+ }
+
+ @Override
+ public Network getActiveNetworkForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getActiveNetworkForUidInternal(uid);
+ }
+
+ private Network getActiveNetworkForUidInternal(final int uid) {
final int user = UserHandle.getUserId(uid);
int vpnNetId = NETID_UNSET;
synchronized (mVpns) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 423f945..ccb4647 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -198,6 +198,7 @@
private long mNextIdleDelay;
private long mNextLightIdleDelay;
private long mNextLightAlarmTime;
+ private long mNextSensingTimeoutAlarmTime;
private long mCurIdleBudget;
private long mMaintenanceStartTime;
@@ -339,6 +340,18 @@
}
};
+ private final AlarmManager.OnAlarmListener mSensingTimeoutAlarmListener
+ = new AlarmManager.OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ if (mState == STATE_SENSING) {
+ synchronized (DeviceIdleController.this) {
+ becomeInactiveIfAppropriateLocked();
+ }
+ }
+ }
+ };
+
private final AlarmManager.OnAlarmListener mDeepAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
@@ -924,6 +937,11 @@
@Override
public void onAnyMotionResult(int result) {
if (DEBUG) Slog.d(TAG, "onAnyMotionResult(" + result + ")");
+ if (result != AnyMotionDetector.RESULT_UNKNOWN) {
+ synchronized (this) {
+ cancelSensingTimeoutAlarmLocked();
+ }
+ }
if (result == AnyMotionDetector.RESULT_MOVED) {
if (DEBUG) Slog.d(TAG, "RESULT_MOVED received.");
synchronized (this) {
@@ -1746,6 +1764,7 @@
mNextIdleDelay = 0;
mNextLightIdleDelay = 0;
cancelAlarmLocked();
+ cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
stopMonitoringMotionLocked();
mAnyMotionDetector.stop();
@@ -1866,15 +1885,16 @@
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, reason);
- scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
+ scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
cancelLocatingLocked();
- mAnyMotionDetector.checkForAnyMotion();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
+ mAnyMotionDetector.checkForAnyMotion();
break;
case STATE_SENSING:
+ cancelSensingTimeoutAlarmLocked();
mState = STATE_LOCATING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
EventLogTags.writeDeviceIdle(mState, reason);
@@ -2161,6 +2181,13 @@
}
}
+ void cancelSensingTimeoutAlarmLocked() {
+ if (mNextSensingTimeoutAlarmTime != 0) {
+ mNextSensingTimeoutAlarmTime = 0;
+ mAlarmManager.cancel(mSensingTimeoutAlarmListener);
+ }
+ }
+
void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
if (mMotionSensor == null) {
@@ -2194,6 +2221,13 @@
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
+ void scheduleSensingTimeoutAlarmLocked(long delay) {
+ if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
+ mNextSensingTimeoutAlarmTime = SystemClock.elapsedRealtime() + delay;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextSensingTimeoutAlarmTime,
+ "DeviceIdleController.sensing", mSensingTimeoutAlarmListener, mHandler);
+ }
+
private static int[] buildAppIdArray(ArrayMap<String, Integer> systemApps,
ArrayMap<String, Integer> userApps, SparseBooleanArray outAppIds) {
outAppIds.clear();
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index d6575e8..aa98648 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -34,6 +34,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
@@ -251,7 +252,8 @@
return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources);
}
- public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive) {
+ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
+ MutableBoolean outLaunched) {
boolean launched = false;
boolean intercept = false;
long doubleTapInterval;
@@ -276,6 +278,7 @@
}
}
MetricsLogger.histogram(mContext, "power_double_tap_interval", (int) doubleTapInterval);
+ outLaunched.value = launched;
return intercept && launched;
}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index ed16af51..4ac75ca 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -56,6 +56,9 @@
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
@@ -68,15 +71,33 @@
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LockSettingsStorage.CredentialHash;
+import libcore.util.HexEncoding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
/**
* Keeps the lock pattern/password data and related settings for each user.
* Used by LockPatternUtils. Needs to be a service because Settings app also needs
@@ -90,6 +111,12 @@
private static final int FBE_ENCRYPTED_NOTIFICATION = 0;
private static final boolean DEBUG = false;
+ private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
+ private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+ private static final int PROFILE_KEY_IV_SIZE = 12;
+ private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
+ private final Object mSeparateChallengeLock = new Object();
+
private final Context mContext;
private final LockSettingsStorage mStorage;
private final LockSettingsStrongAuth mStrongAuth;
@@ -125,6 +152,7 @@
@Override
public void onStart() {
+ AndroidKeyStoreProvider.install();
mLockSettingsService = new LockSettingsService(getContext());
publishBinderService("lock_settings", mLockSettingsService);
}
@@ -149,6 +177,46 @@
}
}
+ /**
+ * Tie managed profile to primary profile if it is in unified mode and not tied before.
+ *
+ * @param managedUserId Managed profile user Id
+ * @param managedUserPassword Managed profile original password (when it has separated lock).
+ * NULL when it does not have a separated lock before.
+ */
+ public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
+ if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
+ // Only for managed profile
+ if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) {
+ return;
+ }
+ // Do not tie managed profile when work challenge is enabled
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ return;
+ }
+ // Do not tie managed profile to parent when it's done already
+ if (mStorage.hasChildProfileLock(managedUserId)) {
+ return;
+ }
+ // Do not tie it to parent when parent does not have a screen lock
+ final int parentId = mUserManager.getProfileParent(managedUserId).id;
+ if (!mStorage.hasPassword(parentId) && !mStorage.hasPattern(parentId)) {
+ if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
+ return;
+ }
+ if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
+ byte[] randomLockSeed = new byte[] {};
+ try {
+ randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
+ String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
+ setLockPasswordInternal(newPassword, managedUserPassword, managedUserId);
+ tieProfileLockToParent(managedUserId, newPassword);
+ } catch (NoSuchAlgorithmException | RemoteException e) {
+ Slog.e(TAG, "Fail to tie managed profile", e);
+ // Nothing client can do to fix this issue, so we do not throw exception out
+ }
+ }
+
public LockSettingsService(Context context) {
mContext = context;
mStrongAuth = new LockSettingsStrongAuth(context);
@@ -254,7 +322,7 @@
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentText(message)
- .setContentInfo(detail)
+ .setSubText(detail)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(intent)
.build();
@@ -271,6 +339,7 @@
}
public void onUnlockUser(int userId) {
+ tieManagedProfileLockIfNecessary(userId, null);
hideEncryptionNotification(new UserHandle(userId));
// Now we have unlocked the parent user we should show notifications
@@ -294,8 +363,7 @@
// Notify keystore that a new user was added.
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
final KeyStore ks = KeyStore.getInstance();
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
- final UserInfo parentInfo = um.getProfileParent(userHandle);
+ final UserInfo parentInfo = mUserManager.getProfileParent(userHandle);
final int parentHandle = parentInfo != null ? parentInfo.id : -1;
ks.onUserAdded(userHandle, parentHandle);
} else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
@@ -343,9 +411,8 @@
// These Settings changed after multi-user was enabled, hence need to be moved per user.
if (getString("migrated_user_specific", null, 0) == null) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
final ContentResolver cr = mContext.getContentResolver();
- List<UserInfo> users = um.getUsers();
+ List<UserInfo> users = mUserManager.getUsers();
for (int user = 0; user < users.size(); user++) {
// Migrate owner info
final int userId = users.get(user).id;
@@ -380,8 +447,7 @@
// Migrates biometric weak such that the fallback mechanism becomes the primary.
if (getString("migrated_biometric_weak", null, 0) == null) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
- List<UserInfo> users = um.getUsers();
+ List<UserInfo> users = mUserManager.getUsers();
for (int i = 0; i < users.size(); i++) {
int userId = users.get(i).id;
long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
@@ -407,9 +473,7 @@
// user was present on the system, so if we're upgrading to M and there is more than one
// user we disable the flag to remain consistent.
if (getString("migrated_lockscreen_disabled", null, 0) == null) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
-
- final List<UserInfo> users = um.getUsers();
+ final List<UserInfo> users = mUserManager.getUsers();
final int userCount = users.size();
int switchableUsers = 0;
for (int i = 0; i < userCount; i++) {
@@ -469,6 +533,27 @@
}
@Override
+ public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+ synchronized (mSeparateChallengeLock) {
+ return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
+ }
+ }
+
+ @Override
+ public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
+ String managedUserPassword) throws RemoteException {
+ synchronized (mSeparateChallengeLock) {
+ setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
+ if (enabled) {
+ mStorage.removeChildProfileLock(userId);
+ removeKeystoreProfileKey(userId);
+ } else {
+ tieManagedProfileLockIfNecessary(userId, managedUserPassword);
+ }
+ }
+ }
+
+ @Override
public void setBoolean(String key, boolean value, int userId) throws RemoteException {
checkWritePermission(userId);
setStringUnchecked(key, userId, value ? "1" : "0");
@@ -536,61 +621,65 @@
@Override
public boolean havePassword(int userId) throws RemoteException {
// Do we need a permissions check here?
-
return mStorage.hasPassword(userId);
}
@Override
public boolean havePattern(int userId) throws RemoteException {
// Do we need a permissions check here?
-
return mStorage.hasPattern(userId);
}
private void setKeystorePassword(String password, int userHandle) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
final KeyStore ks = KeyStore.getInstance();
-
- if (um.getUserInfo(userHandle).isManagedProfile()) {
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
- ks.onUserPasswordChanged(userHandle, password);
- } else {
- throw new RuntimeException("Can't set keystore password on a profile that "
- + "doesn't have a profile challenge.");
- }
- } else {
- final List<UserInfo> profiles = um.getProfiles(userHandle);
- for (UserInfo pi : profiles) {
- // Change password on the given user and all its profiles that don't have
- // their own profile challenge enabled.
- if (pi.id == userHandle || (pi.isManagedProfile()
- && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id))) {
- ks.onUserPasswordChanged(pi.id, password);
- }
- }
- }
+ ks.onUserPasswordChanged(userHandle, password);
}
private void unlockKeystore(String password, int userHandle) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ if (DEBUG) Slog.v(TAG, "Unlock keystore for user: " + userHandle);
final KeyStore ks = KeyStore.getInstance();
+ ks.unlock(userHandle, password);
+ }
- if (um.getUserInfo(userHandle).isManagedProfile()) {
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
- ks.unlock(userHandle, password);
+ private String getDecryptedPasswordForTiedProfile(int userId)
+ throws KeyStoreException, UnrecoverableKeyException,
+ NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
+ CertificateException, IOException {
+ if (DEBUG) Slog.v(TAG, "Unlock keystore for child profile");
+ byte[] storedData = mStorage.readChildProfileLock(userId);
+ if (storedData == null) {
+ throw new FileNotFoundException("Child profile lock file not found");
+ }
+ byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE);
+ byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
+ storedData.length);
+ byte[] decryptionResult;
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey decryptionKey = (SecretKey) keyStore.getKey(
+ PROFILE_KEY_NAME_DECRYPT + userId, null);
+
+ Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
+
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
+ decryptionResult = cipher.doFinal(encryptedPassword);
+ return new String(decryptionResult, StandardCharsets.UTF_8);
+ }
+
+ private void unlockChildProfile(int profileHandle) throws RemoteException {
+ try {
+ doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
+ 0 /* no challenge */, profileHandle);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ if (e instanceof FileNotFoundException) {
+ Slog.i(TAG, "Child profile key not found");
} else {
- throw new RuntimeException("Can't unlock a profile explicitly if it "
- + "doesn't have a profile challenge.");
- }
- } else {
- final List<UserInfo> profiles = um.getProfiles(userHandle);
- for (UserInfo pi : profiles) {
- // Unlock the given user and all its profiles that don't have
- // their own profile challenge enabled.
- if (pi.id == userHandle || (pi.isManagedProfile()
- && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id))) {
- ks.unlock(pi.id, password);
- }
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
}
}
}
@@ -627,6 +716,21 @@
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
+ try {
+ if (!mUserManager.getUserInfo(userId).isManagedProfile()) {
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ for (UserInfo pi : profiles) {
+ // Unlock managed profile with unified lock
+ if (pi.isManagedProfile()
+ && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
+ && mStorage.hasChildProfileLock(pi.id)) {
+ unlockChildProfile(pi.id);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to unlock child profile", e);
+ }
}
private byte[] getCurrentHandle(int userId) {
@@ -661,10 +765,57 @@
return currentHandle;
}
+ private void onUserLockChanged(int userId) throws RemoteException {
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return;
+ }
+ final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ final int size = profiles.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo profile = profiles.get(i);
+ if (profile.isManagedProfile()) {
+ final int managedUserId = profile.id;
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ continue;
+ }
+ if (isSecure) {
+ tieManagedProfileLockIfNecessary(managedUserId, null);
+ } else {
+ getGateKeeperService().clearSecureUserId(managedUserId);
+ mStorage.writePatternHash(null, managedUserId);
+ setKeystorePassword(null, managedUserId);
+ clearUserKeyProtection(managedUserId);
+ mStorage.removeChildProfileLock(managedUserId);
+ removeKeystoreProfileKey(managedUserId);
+ }
+ }
+ }
+ }
+ private boolean isManagedProfileWithUnifiedLock(int userId) {
+ return mUserManager.getUserInfo(userId).isManagedProfile()
+ && !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+ }
+
+ private boolean isManagedProfileWithSeparatedLock(int userId) {
+ return mUserManager.getUserInfo(userId).isManagedProfile()
+ && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+ }
+
+ // This method should be called by LockPatternUtil only, all internal methods in this class
+ // should call setLockPatternInternal.
@Override
public void setLockPattern(String pattern, String savedCredential, int userId)
throws RemoteException {
+ synchronized (mSeparateChallengeLock) {
+ setLockPatternInternal(pattern, savedCredential, userId);
+ setSeparateProfileChallengeEnabled(userId, true, null);
+ }
+ }
+
+ public void setLockPatternInternal(String pattern, String savedCredential, int userId)
+ throws RemoteException {
byte[] currentHandle = getCurrentHandle(userId);
if (pattern == null) {
@@ -672,55 +823,157 @@
mStorage.writePatternHash(null, userId);
setKeystorePassword(null, userId);
clearUserKeyProtection(userId);
+ onUserLockChanged(userId);
return;
}
- if (currentHandle == null) {
- if (savedCredential != null) {
- Slog.w(TAG, "Saved credential provided, but none stored");
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when managed profile has unified lock
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ if (e instanceof FileNotFoundException) {
+ Slog.i(TAG, "Child profile key not found");
+ } else {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
}
- savedCredential = null;
+ } else {
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
}
byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
if (enrolledHandle != null) {
mStorage.writePatternHash(enrolledHandle, userId);
setUserKeyProtection(userId, pattern, verifyPattern(pattern, 0, userId));
+ onUserLockChanged(userId);
} else {
throw new RemoteException("Failed to enroll pattern");
}
}
-
+ // This method should be called by LockPatternUtil only, all internal methods in this class
+ // should call setLockPasswordInternal.
@Override
public void setLockPassword(String password, String savedCredential, int userId)
throws RemoteException {
- byte[] currentHandle = getCurrentHandle(userId);
+ synchronized (mSeparateChallengeLock) {
+ setLockPasswordInternal(password, savedCredential, userId);
+ setSeparateProfileChallengeEnabled(userId, true, null);
+ }
+ }
+ public void setLockPasswordInternal(String password, String savedCredential, int userId)
+ throws RemoteException {
+ byte[] currentHandle = getCurrentHandle(userId);
if (password == null) {
getGateKeeperService().clearSecureUserId(userId);
mStorage.writePasswordHash(null, userId);
setKeystorePassword(null, userId);
clearUserKeyProtection(userId);
+ onUserLockChanged(userId);
return;
}
- if (currentHandle == null) {
- if (savedCredential != null) {
- Slog.w(TAG, "Saved credential provided, but none stored");
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when managed profile has unified lock
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ if (e instanceof FileNotFoundException) {
+ Slog.i(TAG, "Child profile key not found");
+ } else {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
}
- savedCredential = null;
+ } else {
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
}
byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
if (enrolledHandle != null) {
mStorage.writePasswordHash(enrolledHandle, userId);
setUserKeyProtection(userId, password, verifyPassword(password, 0, userId));
+ onUserLockChanged(userId);
} else {
throw new RemoteException("Failed to enroll password");
}
}
+ private void tieProfileLockToParent(int userId, String password) {
+ if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
+ byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
+ byte[] encryptionResult;
+ byte[] iv;
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
+ keyGenerator.init(new SecureRandom());
+ SecretKey secretKey = keyGenerator.generateKey();
+
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.setEntry(
+ PROFILE_KEY_NAME_ENCRYPT + userId,
+ new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ keyStore.setEntry(
+ PROFILE_KEY_NAME_DECRYPT + userId,
+ new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(30)
+ .build());
+
+ // Key imported, obtain a reference to it.
+ SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey(
+ PROFILE_KEY_NAME_ENCRYPT + userId, null);
+ // The original key can now be discarded.
+
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
+ encryptionResult = cipher.doFinal(randomLockSeed);
+ iv = cipher.getIV();
+ } catch (CertificateException | UnrecoverableKeyException
+ | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
+ | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("Failed to encrypt key", e);
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new RuntimeException("Invalid iv length: " + iv.length);
+ }
+ outputStream.write(iv);
+ outputStream.write(encryptionResult);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to concatenate byte arrays", e);
+ }
+ mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
+ }
+
private byte[] enrollCredential(byte[] enrolledHandle,
String enrolledCredential, String toEnroll, int userId)
throws RemoteException {
@@ -820,7 +1073,7 @@
@Override
public void setCredential(String pattern, String oldPattern, int userId)
throws RemoteException {
- setLockPattern(pattern, oldPattern, userId);
+ setLockPatternInternal(pattern, oldPattern, userId);
}
@Override
@@ -838,7 +1091,7 @@
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
&& shouldReEnrollBaseZero) {
- setLockPattern(pattern, patternToVerify, userId);
+ setLockPatternInternal(pattern, patternToVerify, userId);
}
return response;
@@ -857,6 +1110,37 @@
return doVerifyPassword(password, true, challenge, userId);
}
+ @Override
+ public VerifyCredentialResponse verifyTiedProfileChallenge(String password, boolean isPattern,
+ long challenge, int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ if (!isManagedProfileWithUnifiedLock(userId)) {
+ throw new RemoteException("User id must be managed profile with unified lock");
+ }
+ final int parentProfileId = mUserManager.getProfileParent(userId).id;
+ // Unlock parent by using parent's challenge
+ final VerifyCredentialResponse parentResponse = isPattern
+ ? doVerifyPattern(password, true, challenge, parentProfileId)
+ : doVerifyPassword(password, true, challenge, parentProfileId);
+ if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ // Failed, just return parent's response
+ return parentResponse;
+ }
+
+ try {
+ // Unlock work profile, and work profile with unified lock must use password only
+ return doVerifyPassword(getDecryptedPasswordForTiedProfile(userId), true,
+ challenge,
+ userId);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ throw new RemoteException("Unable to get tied profile token");
+ }
+ }
+
private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge,
long challenge, int userId) throws RemoteException {
checkPasswordReadPermission(userId);
@@ -866,7 +1150,7 @@
@Override
public void setCredential(String password, String oldPassword, int userId)
throws RemoteException {
- setLockPassword(password, oldPassword, userId);
+ setLockPasswordInternal(password, oldPassword, userId);
}
@Override
@@ -947,8 +1231,7 @@
" with token length " + response.getPayload().length);
unlockUser(userId, response.getPayload(), secretFromCredential(credential));
- UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+ if (isManagedProfileWithSeparatedLock(userId)) {
TrustManager trustManager =
(TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
trustManager.setDeviceLockedForUser(userId, false);
@@ -1027,6 +1310,23 @@
} catch (RemoteException ex) {
Slog.w(TAG, "unable to clear GK secure user id");
}
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ removeKeystoreProfileKey(userId);
+ }
+ }
+
+ private void removeKeystoreProfileKey(int targetUserId) {
+ if (DEBUG) Slog.v(TAG, "Remove keystore profile key for user: " + targetUserId);
+ try {
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + targetUserId);
+ keyStore.deleteEntry(PROFILE_KEY_NAME_DECRYPT + targetUserId);
+ } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
+ | IOException e) {
+ // We have tried our best to remove all keys
+ Slog.e(TAG, "Unable to remove keystore profile key for user:" + targetUserId, e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 816c791..d136f1a 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -17,7 +17,6 @@
package com.android.server;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
import android.content.ContentValues;
import android.content.Context;
@@ -30,6 +29,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import java.io.File;
import java.io.IOException;
@@ -44,6 +44,7 @@
private static final String TAG = "LockSettingsStorage";
private static final String TABLE = "locksettings";
+ private static final boolean DEBUG = false;
private static final String COLUMN_KEY = "name";
private static final String COLUMN_USERID = "user";
@@ -62,6 +63,7 @@
private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
+ private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
private static final Object DEFAULT = new Object();
@@ -70,8 +72,7 @@
private final Cache mCache = new Cache();
private final Object mFileWriteLock = new Object();
- private int mStoredCredentialType;
- private LockPatternUtils mLockPatternUtils;
+ private SparseArray<Integer> mStoredCredentialType;
class CredentialHash {
static final int TYPE_NONE = -1;
@@ -101,7 +102,7 @@
public LockSettingsStorage(Context context, Callback callback) {
mContext = context;
mOpenHelper = new DatabaseHelper(context, callback);
- mLockPatternUtils = new LockPatternUtils(context);
+ mStoredCredentialType = new SparseArray<Integer>();
}
public void writeKeyValue(String key, String value, int userId) {
@@ -182,32 +183,34 @@
}
public int getStoredCredentialType(int userId) {
- if (mStoredCredentialType != 0) {
- return mStoredCredentialType;
+ final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId);
+ if (cachedStoredCredentialType != null) {
+ return cachedStoredCredentialType.intValue();
}
+ int storedCredentialType;
CredentialHash pattern = readPatternHash(userId);
if (pattern == null) {
if (readPasswordHash(userId) != null) {
- mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ storedCredentialType = CredentialHash.TYPE_PASSWORD;
} else {
- mStoredCredentialType = CredentialHash.TYPE_NONE;
+ storedCredentialType = CredentialHash.TYPE_NONE;
}
} else {
CredentialHash password = readPasswordHash(userId);
if (password != null) {
// Both will never be GateKeeper
if (password.version == CredentialHash.VERSION_GATEKEEPER) {
- mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ storedCredentialType = CredentialHash.TYPE_PASSWORD;
} else {
- mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ storedCredentialType = CredentialHash.TYPE_PATTERN;
}
} else {
- mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ storedCredentialType = CredentialHash.TYPE_PATTERN;
}
}
-
- return mStoredCredentialType;
+ mStoredCredentialType.put(userId, storedCredentialType);
+ return storedCredentialType;
}
@@ -244,6 +247,27 @@
return null;
}
+ public void removeChildProfileLock(int userId) {
+ if (DEBUG)
+ Slog.e(TAG, "Remove child profile lock for user: " + userId);
+ try {
+ deleteFile(getChildProfileLockFile(userId));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void writeChildProfileLock(int userId, byte[] lock) {
+ writeFile(getChildProfileLockFile(userId), lock);
+ }
+
+ public byte[] readChildProfileLock(int userId) {
+ return readFile(getChildProfileLockFile(userId));
+ }
+
+ public boolean hasChildProfileLock(int userId) {
+ return hasFile(getChildProfileLockFile(userId));
+ }
public boolean hasPassword(int userId) {
return hasFile(getLockPasswordFilename(userId)) ||
@@ -321,16 +345,19 @@
}
private void deleteFile(String name) {
- File f = new File(name);
- if (f != null) {
- f.delete();
+ if (DEBUG) Slog.e(TAG, "Delete file " + name);
+ synchronized (mFileWriteLock) {
+ File file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
}
}
public void writePatternHash(byte[] hash, int userId) {
- mStoredCredentialType = hash == null
- ? CredentialHash.TYPE_NONE
- : CredentialHash.TYPE_PATTERN;
+ mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PATTERN);
writeFile(getLockPatternFilename(userId), hash);
clearPasswordHash(userId);
}
@@ -340,9 +367,8 @@
}
public void writePasswordHash(byte[] hash, int userId) {
- mStoredCredentialType = hash == null
- ? CredentialHash.TYPE_NONE
- : CredentialHash.TYPE_PASSWORD;
+ mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PASSWORD);
writeFile(getLockPasswordFilename(userId), hash);
clearPatternHash(userId);
}
@@ -375,8 +401,11 @@
return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
}
+ private String getChildProfileLockFile(int userId) {
+ return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
+ }
+
private String getLockCredentialFilePathForUser(int userId, String basename) {
- userId = getUserParentOrSelfId(userId);
String dataSystemDirectory =
android.os.Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
@@ -388,23 +417,6 @@
}
}
- private int getUserParentOrSelfId(int userId) {
- // Device supports per user encryption, so lock is applied to the given user.
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
- return userId;
- }
- // Device uses Block Based Encryption, and the parent user's lock is used for the whole
- // device.
- if (userId != 0) {
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
- final UserInfo pi = um.getProfileParent(userId);
- if (pi != null) {
- return pi.id;
- }
- }
- return userId;
- }
-
public void removeUser(int userId) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -427,6 +439,9 @@
mCache.putFile(name, null);
}
}
+ } else {
+ // Manged profile
+ removeChildProfileLock(userId);
}
try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1587516..4852788 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10590,14 +10590,14 @@
private boolean requestTargetProviderPermissionsReviewIfNeededLocked(ProviderInfo cpi,
ProcessRecord r, final int userId) {
if (getPackageManagerInternalLocked().isPermissionsReviewRequired(
- cpi.packageName, r.userId)) {
+ cpi.packageName, userId)) {
- final boolean callerForeground = r != null ? r.setSchedGroup
- != ProcessList.SCHED_GROUP_BACKGROUND : true;
+ final boolean callerForeground = r == null || r.setSchedGroup
+ != ProcessList.SCHED_GROUP_BACKGROUND;
// Show a permission review UI only for starting from a foreground app
if (!callerForeground) {
- Slog.w(TAG, "u" + r.userId + " Instantiating a provider in package"
+ Slog.w(TAG, "u" + userId + " Instantiating a provider in package"
+ cpi.packageName + " requires a permissions review");
return false;
}
@@ -10608,7 +10608,7 @@
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
if (DEBUG_PERMISSIONS_REVIEW) {
- Slog.i(TAG, "u" + r.userId + " Launching permission review "
+ Slog.i(TAG, "u" + userId + " Launching permission review "
+ "for package " + cpi.packageName);
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index f659bde..7b2a370 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2043,8 +2043,10 @@
resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
tempDockedTaskInsetBounds);
- if (stack.mFullscreen) {
- // The dock stack went fullscreen which is kinda like dismissing it.
+ // TODO: Checking for isAttached might not be needed as if the user passes in null
+ // dockedBounds then they want the docked stack to be dismissed.
+ if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
+ // The dock stack either was dismissed or went fullscreen, which is kinda the same.
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
@@ -2069,18 +2071,13 @@
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
- if (dockedBounds != null) {
- mWindowManager.getStackDockedModeBounds(
- HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
- }
+ mWindowManager.getStackDockedModeBounds(
+ HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (StackId.isResizeableByDockedStack(i)) {
- ActivityStack otherStack = getStack(i);
- if (otherStack != null) {
- resizeStackLocked(i, dockedBounds != null ? tempRect : null,
- tempOtherTaskBounds, tempOtherTaskInsetBounds, preserveWindows,
- true /* allowResizeInDockedMode */);
- }
+ if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
+ resizeStackLocked(i, tempRect, tempOtherTaskBounds,
+ tempOtherTaskInsetBounds, preserveWindows,
+ true /* allowResizeInDockedMode */);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 3b0b79a..a111bf9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -264,7 +264,12 @@
return true;
}
- // Check if the caller is authorized.
+ // Stop an existing always-on VPN from being dethroned by other apps.
+ if (getAlwaysOnPackage() != null) {
+ return false;
+ }
+
+ // Check that the caller is authorized.
enforceControlPermission();
prepareInternal(newPackage);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index e9d9628..6e7ea99 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -73,7 +73,8 @@
* {@hide}
*/
public final class ContentService extends IContentService.Stub {
- private static final String TAG = "ContentService";
+ static final String TAG = "ContentService";
+ static final boolean DEBUG = false;
public static class Lifecycle extends SystemService {
private ContentService mContentService;
@@ -339,12 +340,10 @@
*/
@Override
public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork,
+ boolean observerWantsSelfNotifications, int flags,
int userHandle) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
- + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
- }
+ if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
+ + " from observer " + observer + ", flags " + Integer.toHexString(flags));
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -373,16 +372,15 @@
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- userHandle, calls);
+ flags, userHandle, calls);
}
final int numCalls = calls.size();
for (int i=0; i<numCalls; i++) {
ObserverCall oc = calls.get(i);
try {
oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
- }
+ if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
+ + uri);
} catch (RemoteException ex) {
synchronized (mRootNode) {
Log.w(TAG, "Found dead observer, removing");
@@ -401,7 +399,7 @@
}
}
}
- if (syncToNetwork) {
+ if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
@@ -420,7 +418,8 @@
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, boolean syncToNetwork) {
- notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
+ notifyChange(uri, observer, observerWantsSelfNotifications,
+ syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0,
UserHandle.getCallingUserId());
}
@@ -1064,14 +1063,14 @@
for (int i = 0; i < packageCache.size();) {
final Pair<String, Uri> key = packageCache.keyAt(i);
if (key.second != null && key.second.toString().startsWith(uri.toString())) {
- Slog.d(TAG, "Invalidating cache for key " + key);
+ if (DEBUG) Slog.d(TAG, "Invalidating cache for key " + key);
packageCache.removeAt(i);
} else {
i++;
}
}
} else {
- Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
+ if (DEBUG) Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
packageCache.clear();
}
}
@@ -1310,8 +1309,8 @@
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
- boolean observerWantsSelfNotifications, int targetUserHandle,
- ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, int flags,
+ int targetUserHandle, ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
@@ -1329,9 +1328,29 @@
|| entry.userHandle == UserHandle.USER_ALL
|| targetUserHandle == entry.userHandle) {
// Make sure the observer is interested in the notification
- if (leaf || (!leaf && entry.notifyForDescendants)) {
- calls.add(new ObserverCall(this, entry.observer, selfChange));
+ if (leaf) {
+ // If we are at the leaf: we always report, unless the sender has asked
+ // to skip observers that are notifying for descendants (since they will
+ // be sending another more specific URI for them).
+ if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0
+ && entry.notifyForDescendants) {
+ if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+ + ": skip notify for descendants");
+ continue;
+ }
+ } else {
+ // If we are not at the leaf: we report if the observer says it wants
+ // to be notified for all descendants.
+ if (!entry.notifyForDescendants) {
+ if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+ + ": not monitor descendants");
+ continue;
+ }
}
+ if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+ + " flags=" + Integer.toHexString(flags)
+ + " desc=" + entry.notifyForDescendants);
+ calls.add(new ObserverCall(this, entry.observer, selfChange));
}
}
}
@@ -1340,19 +1359,22 @@
* targetUserHandle is either a hard user handle or is USER_ALL
*/
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
- boolean observerWantsSelfNotifications, int targetUserHandle,
- ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, int flags,
+ int targetUserHandle, ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
+ if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
- targetUserHandle, calls);
+ flags, targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
+ if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+ + segment);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
- targetUserHandle, calls);
+ flags, targetUserHandle, calls);
}
int N = mChildren.size();
@@ -1360,8 +1382,8 @@
ObserverNode node = mChildren.get(i);
if (segment == null || node.mName.equals(segment)) {
// We found the child,
- node.collectObserversLocked(uri, index + 1,
- observer, observerWantsSelfNotifications, targetUserHandle, calls);
+ node.collectObserversLocked(uri, index + 1, observer,
+ observerWantsSelfNotifications, flags, targetUserHandle, calls);
if (segment != null) {
break;
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index e5342ce..db41a54 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1402,12 +1402,24 @@
}
}
+ private void restoreLostPeriodicSyncsIfNeeded(int userId) {
+ List<SyncOperation> periodicSyncs = new ArrayList<SyncOperation>();
+ for (SyncOperation sync : getAllPendingSyncs()) {
+ if (sync.isPeriodic && sync.target.userId == userId) {
+ periodicSyncs.add(sync);
+ }
+ }
+ mSyncStorageEngine.restorePeriodicSyncsIfNeededForUser(userId, periodicSyncs);
+ }
+
private void onUserUnlocked(int userId) {
// Make sure that accounts we're about to use are valid.
AccountManagerService.getSingleton().validateAccounts(userId);
mSyncAdapters.invalidateCache(userId);
+ restoreLostPeriodicSyncsIfNeeded(userId);
+
EndPoint target = new EndPoint(null, null, userId);
updateRunningAccounts(target);
@@ -2578,9 +2590,11 @@
}
}
+ // Cancel all jobs from non-existent accounts.
+ AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
List<SyncOperation> ops = getAllPendingSyncs();
for (SyncOperation op: ops) {
- if (!containsAccountAndUser(accounts, op.target.account, op.target.userId)) {
+ if (!containsAccountAndUser(allAccounts, op.target.account, op.target.userId)) {
getJobScheduler().cancel(op.jobId);
}
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index bc3fc6a..fb23265 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -826,6 +826,35 @@
return true;
}
+ /**
+ * STOPSHIP This is a temporary workaround and should be removed before shipping: b/28052438
+ */
+ void restorePeriodicSyncsIfNeededForUser(int userHandle, List<SyncOperation> periodicSyncs) {
+ if (mPeriodicSyncAddedListener == null) {
+ return;
+ }
+ synchronized (mAuthorities) {
+ for (int i = 0; i < mAuthorities.size(); i++) {
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ if (authority.target.userId == userHandle && authority.enabled) {
+ boolean periodicSyncAlreadyExists = false;
+ for (SyncOperation sync : periodicSyncs) {
+ if (authority.target.matchesSpec(sync.target)) {
+ periodicSyncAlreadyExists = true;
+ break;
+ }
+ }
+ // The periodic sync must have been lost due to previous bug.
+ if (!periodicSyncAlreadyExists) {
+ mPeriodicSyncAddedListener.onPeriodicSyncAdded(authority.target,
+ new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS,
+ calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS));
+ }
+ }
+ }
+ }
+ }
+
public void setMasterSyncAutomatically(boolean flag, int userId) {
synchronized (mAuthorities) {
Boolean auto = mMasterSyncAutomatically.get(userId);
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 7b134ca..3d8bf51 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -107,6 +107,7 @@
private static final int FINGERPRINT_ACQUIRED_GOOD = 0;
private final String mKeyguardPackage;
private int mCurrentUserId = UserHandle.USER_CURRENT;
+ private int mUserIdForRemove = UserHandle.USER_NULL;
Handler mHandler = new Handler() {
@Override
@@ -205,10 +206,12 @@
protected void handleRemoved(long deviceId, int fingerId, int groupId) {
final ClientMonitor client = mRemoveClient;
if (fingerId != 0) {
- removeTemplateForUser(mRemoveClient, fingerId);
+ removeTemplateForUser(mUserIdForRemove, fingerId);
+ } else {
+ mUserIdForRemove = UserHandle.USER_NULL;
}
if (client != null && client.sendRemoved(fingerId, groupId)) {
- removeClient(mRemoveClient);
+ removeClient(client);
}
}
@@ -325,8 +328,8 @@
return false;
}
- private void removeTemplateForUser(ClientMonitor clientMonitor, int fingerId) {
- mFingerprintUtils.removeFingerprintIdForUser(mContext, fingerId, clientMonitor.userId);
+ private void removeTemplateForUser(int userId, int fingerId) {
+ mFingerprintUtils.removeFingerprintIdForUser(mContext, fingerId, userId);
}
private void addTemplateForUser(ClientMonitor clientMonitor, int fingerId) {
@@ -488,6 +491,7 @@
stopPendingOperations(true);
mRemoveClient = new ClientMonitor(token, receiver, userId, restricted, token.toString());
+ mUserIdForRemove = mCurrentUserId;
// The fingerprint template ids will be removed when we get confirmation from the HAL
try {
final int result = daemon.remove(fingerId, userId);
@@ -943,10 +947,6 @@
if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName);
return;
}
-
- // Group ID is arbitrarily set to parent profile user ID. It just represents
- // the default fingerprints for the user.
- final int effectiveGroupId = getEffectiveUserId(groupId);
final int realUserId = Binder.getCallingUid();
final boolean restricted = isRestricted();
@@ -954,7 +954,7 @@
@Override
public void run() {
MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0);
- startAuthentication(token, opId, realUserId, effectiveGroupId, receiver,
+ startAuthentication(token, opId, realUserId, groupId, receiver,
flags, restricted, opPackageName);
}
});
@@ -989,14 +989,10 @@
final IFingerprintServiceReceiver receiver) {
checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
final boolean restricted = isRestricted();
-
- // Group ID is arbitrarily set to parent profile user ID. It just represents
- // the default fingerprints for the user.
- final int effectiveGroupId = getEffectiveUserId(groupId);
mHandler.post(new Runnable() {
@Override
public void run() {
- startRemove(token, fingerId, effectiveGroupId, receiver, restricted);
+ startRemove(token, fingerId, groupId, receiver, restricted);
}
});
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e73beaa..c7c765bb 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -200,6 +200,7 @@
private static native int nativeInjectInputEvent(long ptr, InputEvent event, int displayId,
int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
int policyFlags);
+ private static native void nativeToggleCapsLock(long ptr, int deviceId);
private static native void nativeSetInputWindows(long ptr, InputWindowHandle[] windowHandles);
private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
private static native void nativeSetSystemUiVisibility(long ptr, int visibility);
@@ -2279,5 +2280,10 @@
mHandler.obtainMessage(MSG_INPUT_METHOD_SUBTYPE_CHANGED, userId, 0, someArgs)
.sendToTarget();
}
+
+ @Override
+ public void toggleCapsLock(int deviceId) {
+ nativeToggleCapsLock(mPtr, deviceId);
+ }
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index fa8620f..b235002 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -304,7 +304,7 @@
toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
if (toCancel != null) {
- cancelJobImpl(toCancel);
+ cancelJobImpl(toCancel, jobStatus);
}
startTrackingJob(jobStatus, toCancel);
}
@@ -331,7 +331,7 @@
}
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
- cancelJobImpl(toRemove);
+ cancelJobImpl(toRemove, null);
}
}
@@ -360,7 +360,7 @@
} catch (RemoteException e) {
}
}
- cancelJobImpl(toRemove);
+ cancelJobImpl(toRemove, null);
}
}
@@ -377,13 +377,13 @@
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
}
if (toCancel != null) {
- cancelJobImpl(toCancel);
+ cancelJobImpl(toCancel, null);
}
}
- private void cancelJobImpl(JobStatus cancelled) {
+ private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
- stopTrackingJob(cancelled, true /* writeBack */);
+ stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
synchronized (mLock) {
// Remove from pending queue.
mPendingJobs.remove(cancelled);
@@ -549,7 +549,7 @@
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
if (update) {
- controller.maybeStopTrackingJobLocked(jobStatus, true);
+ controller.maybeStopTrackingJobLocked(jobStatus, null, true);
}
controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
}
@@ -561,14 +561,15 @@
* Called when we want to remove a JobStatus object that we've finished executing. Returns the
* object removed.
*/
- private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
+ private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
+ boolean writeBack) {
synchronized (mLock) {
// Remove from store as well as controllers.
final boolean removed = mJobs.remove(jobStatus, writeBack);
if (removed && mReadyToRock) {
for (int i=0; i<mControllers.size(); i++) {
StateController controller = mControllers.get(i);
- controller.maybeStopTrackingJobLocked(jobStatus, false);
+ controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
}
}
return removed;
@@ -696,7 +697,7 @@
}
// Do not write back immediately if this is a periodic job. The job may get lost if system
// shuts down before it is added back.
- if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
+ if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
@@ -780,7 +781,7 @@
}
break;
case MSG_STOP_JOB:
- cancelJobImpl((JobStatus)message.obj);
+ cancelJobImpl((JobStatus)message.obj, null);
break;
}
maybeRunPendingJobsH();
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 2114fc3..d8490d4 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -77,7 +77,7 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
mTrackedTasks.remove(jobStatus);
}
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index ac9f425..0772364 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -87,7 +87,7 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
if (taskStatus.hasChargingConstraint()) {
mTrackedTasks.remove(taskStatus);
}
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 6ef425a..5ad8189 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -92,7 +92,7 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
mTrackedJobs.remove(jobStatus);
}
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index c5cf30f..b2f1958 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -84,17 +84,8 @@
boolean havePendingUris = false;
// If there is a previous job associated with the new job, propagate over
// any pending content URI trigger reports.
- if (lastJob != null && lastJob.contentObserverJobInstance != null
- && lastJob.contentObserverJobInstance
- != taskStatus.contentObserverJobInstance
- && lastJob.contentObserverJobInstance.mChangedAuthorities != null) {
+ if (taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
havePendingUris = true;
- taskStatus.contentObserverJobInstance.mChangedAuthorities
- = lastJob.contentObserverJobInstance.mChangedAuthorities;
- taskStatus.contentObserverJobInstance.mChangedUris
- = lastJob.contentObserverJobInstance.mChangedUris;
- lastJob.contentObserverJobInstance.mChangedAuthorities = null;
- lastJob.contentObserverJobInstance.mChangedUris = null;
}
// If we have previously reported changed authorities/uris, then we failed
// to complete the job with them so will re-record them to report again.
@@ -138,15 +129,34 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
+ boolean forUpdate) {
if (taskStatus.hasContentTriggerConstraint()) {
- if (!forUpdate) {
- // We won't do this reset if being called for an update, because
- // we know it will be immediately followed by maybeStartTrackingJobLocked...
- // and we don't want to lose any content changes in-between.
- if (taskStatus.contentObserverJobInstance != null) {
- taskStatus.contentObserverJobInstance.detach();
- taskStatus.contentObserverJobInstance = null;
+ if (taskStatus.contentObserverJobInstance != null) {
+ if (incomingJob != null && taskStatus.contentObserverJobInstance != null
+ && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
+ // We are stopping this job, but it is going to be replaced by this given
+ // incoming job. We want to propagate our state over to it, so we don't
+ // lose any content changes that had happend since the last one started.
+ // If there is a previous job associated with the new job, propagate over
+ // any pending content URI trigger reports.
+ if (incomingJob.contentObserverJobInstance == null) {
+ incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
+ }
+ incomingJob.contentObserverJobInstance.mChangedAuthorities
+ = taskStatus.contentObserverJobInstance.mChangedAuthorities;
+ incomingJob.contentObserverJobInstance.mChangedUris
+ = taskStatus.contentObserverJobInstance.mChangedUris;
+ taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
+ taskStatus.contentObserverJobInstance.mChangedUris = null;
+ } else {
+ // We won't do this reset if being called for an update, because
+ // we know it will be immediately followed by maybeStartTrackingJobLocked...
+ // and we don't want to lose any content changes in-between.
+ if (taskStatus.contentObserverJobInstance != null) {
+ taskStatus.contentObserverJobInstance.detach();
+ taskStatus.contentObserverJobInstance = null;
+ }
}
}
mTrackedTasks.remove(taskStatus);
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index d2ef6a2..64887e8 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -166,7 +166,7 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
mTrackedTasks.remove(jobStatus);
}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index d9eb45c..50aa882 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -75,7 +75,7 @@
}
@Override
- public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
mTrackedTasks.remove(taskStatus);
}
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index ac7f4c3..0139039 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -56,7 +56,8 @@
/**
* Remove task - this will happen if the task is cancelled, completed, etc.
*/
- public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate);
+ public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+ boolean forUpdate);
/**
* Called when a new job is being created to reschedule an old failed job.
*/
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 3543249..ab6768e 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -74,7 +74,7 @@
@Override
public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
- maybeStopTrackingJobLocked(job, false);
+ maybeStopTrackingJobLocked(job, null, false);
boolean isInsert = false;
ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
while (it.hasPrevious()) {
@@ -101,7 +101,7 @@
* Really an == comparison should be enough, but why play with fate? We'll do <=.
*/
@Override
- public void maybeStopTrackingJobLocked(JobStatus job, boolean forUpdate) {
+ public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob, boolean forUpdate) {
if (mTrackedJobs.remove(job)) {
checkExpiredDelaysAndResetAlarm();
checkExpiredDeadlinesAndResetAlarm();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 257c7da..5953dde 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -18,12 +18,17 @@
import com.android.server.SystemService;
import com.android.server.vr.VrManagerInternal;
+import com.android.server.vr.VrManagerService;
import com.android.server.vr.VrStateListener;
import android.content.Context;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
+import android.os.RemoteException;
import android.os.Trace;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.util.Slog;
public class LightsService extends SystemService {
@@ -164,13 +169,19 @@
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
- getLocalService(VrManagerInternal.class).registerListener(mVrStateListener);
+ IVrManager vrManager =
+ (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE);
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
}
}
- private final VrStateListener mVrStateListener = new VrStateListener() {
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
- public void onVrStateChanged(boolean enabled) {
+ public void onVrStateChanged(boolean enabled) throws RemoteException {
LightImpl l = mLights[LightsManager.LIGHT_ID_BACKLIGHT];
if (enabled) {
if (DEBUG) Slog.v(TAG, "VR mode enabled, setting brightness to low persistence");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ef53905..6cdc40f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -81,6 +81,7 @@
import java.io.FileDescriptor;
import java.io.FileFilter;
import java.io.IOException;
+import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -151,6 +152,7 @@
private String mPackageName;
private int mVersionCode;
private Signature[] mSignatures;
+ private Certificate[][] mCertificates;
/**
* Path to the validated base APK for this session, which may point at an
@@ -633,7 +635,7 @@
mRelinquished = true;
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
- installerPackageName, installerUid, user);
+ installerPackageName, installerUid, user, mCertificates);
}
/**
@@ -695,6 +697,7 @@
}
if (mSignatures == null) {
mSignatures = apk.signatures;
+ mCertificates = apk.certificates;
}
assertApkConsistent(String.valueOf(addedFile), apk);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fbf4c0c..a6fce51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -110,6 +110,7 @@
import android.app.admin.IDevicePolicyManager;
import android.app.admin.SecurityLog;
import android.app.backup.IBackupManager;
+import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -277,6 +278,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
@@ -7005,11 +7007,28 @@
pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);
}
+ UsageStatsManager usageMgr =
+ (UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE);
+
int curr = 0;
int total = pkgs.size();
for (PackageParser.Package pkg : pkgs) {
curr++;
+ if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipping update of of non-optimizable app " + pkg.packageName);
+ }
+ continue;
+ }
+
+ if (!causeFirstBoot && usageMgr.isAppInactive(pkg.packageName)) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipping update of of idle app " + pkg.packageName);
+ }
+ continue;
+ }
+
if (DEBUG_DEXOPT) {
Log.i(TAG, "Extracting app " + curr + " of " + total + ": " + pkg.packageName);
}
@@ -7023,16 +7042,11 @@
}
}
- if (PackageDexOptimizer.canOptimizePackage(pkg)) {
- // If the cache was pruned, any compiled odex files will likely be out of date
- // and would have to be patched (would be SELF_PATCHOAT, which is deprecated).
- // Instead, force the extraction in this case.
- performDexOpt(pkg.packageName,
- null /* instructionSet */,
- false /* checkProfiles */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
- false /* force */);
- }
+ performDexOpt(pkg.packageName,
+ null /* instructionSet */,
+ false /* checkProfiles */,
+ causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+ false /* force */);
}
}
@@ -10964,7 +10978,8 @@
null /*originatingUri*/, null /*referrer*/, -1 /*originatingUid*/, callingUid);
final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer,
installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user,
- null /*packageAbiOverride*/, null /*grantedPermissions*/);
+ null /*packageAbiOverride*/, null /*grantedPermissions*/,
+ null /*certificates*/);
params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
@@ -10978,7 +10993,8 @@
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
- String installerPackageName, int installerUid, UserHandle user) {
+ String installerPackageName, int installerUid, UserHandle user,
+ Certificate[][] certificates) {
if (DEBUG_EPHEMERAL) {
if ((sessionParams.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
Slog.d(TAG, "Ephemeral install of " + packageName);
@@ -10999,7 +11015,7 @@
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
- sessionParams.grantedRuntimePermissions);
+ sessionParams.grantedRuntimePermissions, certificates);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
@@ -12094,11 +12110,12 @@
final String packageAbiOverride;
final String[] grantedRuntimePermissions;
final VerificationInfo verificationInfo;
+ final Certificate[][] certificates;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
- String[] grantedPermissions) {
+ String[] grantedPermissions, Certificate[][] certificates) {
super(user);
this.origin = origin;
this.move = move;
@@ -12109,6 +12126,7 @@
this.verificationInfo = verificationInfo;
this.packageAbiOverride = packageAbiOverride;
this.grantedRuntimePermissions = grantedPermissions;
+ this.certificates = certificates;
}
@Override
@@ -12577,6 +12595,7 @@
/** If non-null, drop an async trace when the install completes */
final String traceMethod;
final int traceCookie;
+ final Certificate[][] certificates;
// The list of instruction sets supported by this app. This is currently
// only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -12587,7 +12606,7 @@
int installFlags, String installerPackageName, String volumeUuid,
UserHandle user, String[] instructionSets,
String abiOverride, String[] installGrantPermissions,
- String traceMethod, int traceCookie) {
+ String traceMethod, int traceCookie, Certificate[][] certificates) {
this.origin = origin;
this.move = move;
this.installFlags = installFlags;
@@ -12600,6 +12619,7 @@
this.installGrantPermissions = installGrantPermissions;
this.traceMethod = traceMethod;
this.traceCookie = traceCookie;
+ this.certificates = certificates;
}
abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException;
@@ -12692,9 +12712,9 @@
FileInstallArgs(InstallParams params) {
super(params.origin, params.move, params.observer, params.installFlags,
params.installerPackageName, params.volumeUuid,
- params.getUser(), null /* instruction sets */, params.packageAbiOverride,
+ params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie);
+ params.traceMethod, params.traceCookie, params.certificates);
if (isFwdLocked()) {
throw new IllegalArgumentException("Forward locking only supported in ASEC");
}
@@ -12703,7 +12723,7 @@
/** Existing install */
FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
- null, null, null, 0);
+ null, null, null, 0, null /*certificates*/);
this.codeFile = (codePath != null) ? new File(codePath) : null;
this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
}
@@ -12928,15 +12948,15 @@
params.installerPackageName, params.volumeUuid,
params.getUser(), null /* instruction sets */, params.packageAbiOverride,
params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie);
+ params.traceMethod, params.traceCookie, params.certificates);
}
/** Existing install */
AsecInstallArgs(String fullCodePath, String[] instructionSets,
boolean isExternal, boolean isForwardLocked) {
super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
- instructionSets, null, null, null, 0);
+ | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+ instructionSets, null, null, null, 0, null /*certificates*/);
// Hackily pretend we're still looking at a full code path
if (!fullCodePath.endsWith(RES_FILE_NAME)) {
fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath();
@@ -12952,8 +12972,8 @@
AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
- instructionSets, null, null, null, 0);
+ | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+ instructionSets, null, null, null, 0, null /*certificates*/);
this.cid = cid;
setMountPath(PackageHelper.getSdDir(cid));
}
@@ -13222,7 +13242,7 @@
params.installerPackageName, params.volumeUuid,
params.getUser(), null /* instruction sets */, params.packageAbiOverride,
params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie);
+ params.traceMethod, params.traceCookie, params.certificates);
}
int copyApk(IMediaContainerService imcs, boolean temp) {
@@ -14261,7 +14281,18 @@
}
try {
- PackageParser.collectCertificates(pkg, parseFlags);
+ // either use what we've been given or parse directly from the APK
+ if (args.certificates != null) {
+ try {
+ PackageParser.populateCertificates(pkg, args.certificates);
+ } catch (PackageParserException e) {
+ // there was something wrong with the certificates we were given;
+ // try to pull them from the APK
+ PackageParser.collectCertificates(pkg, parseFlags);
+ }
+ } else {
+ PackageParser.collectCertificates(pkg, parseFlags);
+ }
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
@@ -19146,7 +19177,7 @@
final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
installerPackageName, volumeUuid, null /*verificationInfo*/, user,
- packageAbiOverride, null);
+ packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/);
params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 06a91fb..60a0d62 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -467,13 +467,16 @@
@Override
public List<UserInfo> getProfiles(int userId, boolean enabledOnly) {
+ boolean returnFullInfo = true;
if (userId != UserHandle.getCallingUserId()) {
checkManageUsersPermission("getting profiles related to user " + userId);
+ } else {
+ returnFullInfo = hasManageUsersPermission();
}
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mUsersLock) {
- return getProfilesLU(userId, enabledOnly);
+ return getProfilesLU(userId, enabledOnly, returnFullInfo);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -481,7 +484,7 @@
}
/** Assume permissions already checked and caller's identity cleared */
- private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly) {
+ private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly, boolean fullInfo) {
UserInfo user = getUserInfoLU(userId);
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
if (user == null) {
@@ -503,7 +506,14 @@
if (profile.partial) {
continue;
}
- users.add(userWithName(profile));
+ UserInfo userInfo = userWithName(profile);
+ // If full info is not required - clear PII data to prevent 3P apps from reading it
+ if (!fullInfo) {
+ userInfo = new UserInfo(userInfo);
+ userInfo.name = null;
+ userInfo.iconPath = null;
+ }
+ users.add(userInfo);
}
return users;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 14d0457..fb56a0c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -69,6 +69,7 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.input.InputManagerInternal;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -107,6 +108,7 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.MutableBoolean;
import android.util.Slog;
import android.util.SparseArray;
import android.util.LongSparseArray;
@@ -304,6 +306,7 @@
WindowManagerInternal mWindowManagerInternal;
PowerManager mPowerManager;
ActivityManagerInternal mActivityManagerInternal;
+ InputManagerInternal mInputManagerInternal;
DreamManagerInternal mDreamManagerInternal;
PowerManagerInternal mPowerManagerInternal;
IStatusBarService mStatusBarService;
@@ -396,6 +399,8 @@
volatile boolean mBeganFromNonInteractive;
volatile int mPowerKeyPressCounter;
volatile boolean mEndCallKeyHandled;
+ volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
+ volatile boolean mGoingToSleep;
boolean mRecentsVisible;
int mRecentAppsHeldModifiers;
@@ -601,6 +606,9 @@
boolean mConsumeSearchKeyUp;
boolean mAssistKeyLongPressed;
boolean mPendingMetaAction;
+ boolean mPendingCapsLockToggle;
+ int mMetaState;
+ int mInitialMetaState;
boolean mForceShowSystemBars;
// support for activating the lock screen while the screen is on
@@ -685,6 +693,7 @@
= new LogDecelerateInterpolator(100, 0);
private boolean mForceWindowDrawsStatusBarBackground;
+ private final MutableBoolean mTmpBoolean = new MutableBoolean(false);
private static final int MSG_ENABLE_POINTER_LOCATION = 1;
private static final int MSG_DISABLE_POINTER_LOCATION = 2;
@@ -1032,7 +1041,11 @@
GestureLauncherService.class);
boolean gesturedServiceIntercepted = false;
if (gestureService != null) {
- gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive);
+ gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
+ mTmpBoolean);
+ if (mTmpBoolean.value && mGoingToSleep) {
+ mCameraGestureTriggeredDuringGoingToSleep = true;
+ }
}
// If the power key has still not yet been handled, then detect short
@@ -1486,6 +1499,7 @@
mWindowManagerFuncs = windowManagerFuncs;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1817,41 +1831,6 @@
}
}
- mStatusBarHeight =
- res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-
- // Height of the navigation bar when presented horizontally at bottom
- mNavigationBarHeightForRotationDefault[mPortraitRotation] =
- mNavigationBarHeightForRotationDefault[mUpsideDownRotation] =
- res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
- mNavigationBarHeightForRotationDefault[mLandscapeRotation] =
- mNavigationBarHeightForRotationDefault[mSeascapeRotation] = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_landscape);
-
- // Width of the navigation bar when presented vertically along one side
- mNavigationBarWidthForRotationDefault[mPortraitRotation] =
- mNavigationBarWidthForRotationDefault[mUpsideDownRotation] =
- mNavigationBarWidthForRotationDefault[mLandscapeRotation] =
- mNavigationBarWidthForRotationDefault[mSeascapeRotation] =
- res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
-
- // Height of the navigation bar when presented horizontally at bottom
- mNavigationBarHeightForRotationInCarMode[mPortraitRotation] =
- mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_car_mode);
- mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] =
- mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode);
-
- // Width of the navigation bar when presented vertically along one side
- mNavigationBarWidthForRotationInCarMode[mPortraitRotation] =
- mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] =
- mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] =
- mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_width_car_mode);
-
// SystemUI (status bar) layout policy
int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density;
int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / density;
@@ -1860,6 +1839,7 @@
mNavigationBarCanMove = width != height && shortSizeDp < 600;
mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+
// Allow a system property to override this. Used by the emulator.
// See also hasNavigationBar().
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
@@ -2290,6 +2270,46 @@
}
}
+ @Override
+ public void onConfigurationChanged() {
+ final Resources res = mContext.getResources();
+
+ mStatusBarHeight =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+
+ // Height of the navigation bar when presented horizontally at bottom
+ mNavigationBarHeightForRotationDefault[mPortraitRotation] =
+ mNavigationBarHeightForRotationDefault[mUpsideDownRotation] =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+ mNavigationBarHeightForRotationDefault[mLandscapeRotation] =
+ mNavigationBarHeightForRotationDefault[mSeascapeRotation] = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape);
+
+ // Width of the navigation bar when presented vertically along one side
+ mNavigationBarWidthForRotationDefault[mPortraitRotation] =
+ mNavigationBarWidthForRotationDefault[mUpsideDownRotation] =
+ mNavigationBarWidthForRotationDefault[mLandscapeRotation] =
+ mNavigationBarWidthForRotationDefault[mSeascapeRotation] =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+
+ // Height of the navigation bar when presented horizontally at bottom
+ mNavigationBarHeightForRotationInCarMode[mPortraitRotation] =
+ mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] =
+ res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_car_mode);
+ mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] =
+ mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode);
+
+ // Width of the navigation bar when presented vertically along one side
+ mNavigationBarWidthForRotationInCarMode[mPortraitRotation] =
+ mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] =
+ mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] =
+ mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] =
+ res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width_car_mode);
+ }
+
/** {@inheritDoc} */
@Override
public int windowTypeToLayerLw(int type) {
@@ -2954,6 +2974,10 @@
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
+ // Any key that is not Alt or Meta cancels Caps Lock combo tracking.
+ if (mPendingCapsLockToggle && !KeyEvent.isMetaKey(keyCode) && !KeyEvent.isAltKey(keyCode)) {
+ mPendingCapsLockToggle = false;
+ }
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
@@ -3202,6 +3226,38 @@
}
}
+ // Toggle Caps Lock on META-ALT.
+ boolean actionTriggered = false;
+ if (KeyEvent.isModifierKey(keyCode)) {
+ if (!mPendingCapsLockToggle) {
+ // Start tracking meta state for combo.
+ mInitialMetaState = mMetaState;
+ mPendingCapsLockToggle = true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ int altOnMask = mMetaState & KeyEvent.META_ALT_MASK;
+ int metaOnMask = mMetaState & KeyEvent.META_META_MASK;
+
+ // Check for Caps Lock toggle
+ if ((metaOnMask != 0) && (altOnMask != 0)) {
+ // Check if nothing else is pressed
+ if (mInitialMetaState == (mMetaState ^ (altOnMask | metaOnMask))) {
+ // Handle Caps Lock Toggle
+ mInputManagerInternal.toggleCapsLock(event.getDeviceId());
+ actionTriggered = true;
+ }
+ }
+
+ // Always stop tracking when key goes up.
+ mPendingCapsLockToggle = false;
+ }
+ }
+ // Store current meta state to be able to evaluate it later.
+ mMetaState = metaState;
+
+ if (actionTriggered) {
+ return -1;
+ }
+
if (KeyEvent.isMetaKey(keyCode)) {
if (down) {
mPendingMetaAction = true;
@@ -5948,6 +6004,8 @@
@Override
public void startedGoingToSleep(int why) {
if (DEBUG_WAKEUP) Slog.i(TAG, "Started going to sleep... (why=" + why + ")");
+ mCameraGestureTriggeredDuringGoingToSleep = false;
+ mGoingToSleep = true;
if (mKeyguardDelegate != null) {
mKeyguardDelegate.onStartedGoingToSleep(why);
}
@@ -5960,6 +6018,8 @@
if (DEBUG_WAKEUP) Slog.i(TAG, "Finished going to sleep... (why=" + why + ")");
MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000);
+ mGoingToSleep = false;
+
// We must get this work done here because the power manager will drop
// the wake lock and let the system suspend once this function returns.
synchronized (mLock) {
@@ -5969,8 +6029,10 @@
updateLockScreenTimeout();
}
if (mKeyguardDelegate != null) {
- mKeyguardDelegate.onFinishedGoingToSleep(why);
+ mKeyguardDelegate.onFinishedGoingToSleep(why,
+ mCameraGestureTriggeredDuringGoingToSleep);
}
+ mCameraGestureTriggeredDuringGoingToSleep = false;
}
// Called on the PowerManager's Notifier thread.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 8d296d5..52e5880 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -294,9 +294,9 @@
mKeyguardState.interactiveState = INTERACTIVE_STATE_GOING_TO_SLEEP;
}
- public void onFinishedGoingToSleep(int why) {
+ public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) {
if (mKeyguardService != null) {
- mKeyguardService.onFinishedGoingToSleep(why);
+ mKeyguardService.onFinishedGoingToSleep(why, cameraGestureTriggered);
}
mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP;
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 429b188..dacdec0 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -117,9 +117,9 @@
}
@Override
- public void onFinishedGoingToSleep(int reason) {
+ public void onFinishedGoingToSleep(int reason, boolean cameraGestureTriggered) {
try {
- mService.onFinishedGoingToSleep(reason);
+ mService.onFinishedGoingToSleep(reason, cameraGestureTriggered);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7570960..8cd536d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -53,6 +53,8 @@
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -72,6 +74,7 @@
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.vr.VrManagerInternal;
+import com.android.server.vr.VrManagerService;
import com.android.server.vr.VrStateListener;
import libcore.util.Objects;
@@ -658,7 +661,13 @@
resolver.registerContentObserver(Settings.Secure.getUriFor(
Secure.BRIGHTNESS_USE_TWILIGHT),
false, mSettingsObserver, UserHandle.USER_ALL);
- getLocalService(VrManagerInternal.class).registerListener(mVrStateListener);
+ IVrManager vrManager =
+ (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE);
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -3007,7 +3016,7 @@
}
}
- private final VrStateListener mVrStateListener = new VrStateListener() {
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
powerHintInternal(POWER_HINT_VR_MODE, enabled ? 1 : 0);
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 858f7c7..9c2c6bf 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -375,7 +375,7 @@
} else {
mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null);
}
- final long maxTimeToLock = dpm.getMaximumTimeToLock(null);
+ final long maxTimeToLock = dpm.getMaximumTimeToLockForUserAndProfiles(mUserId);
if (maxTimeToLock != mMaximumTimeToLock) {
// If the timeout changes, cancel the alarm and send a timeout event to have
// the agent re-evaluate trust.
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index 93bb9d7..1bbb9f5 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -31,13 +31,6 @@
public static final int NO_ERROR = 0;
/**
- * Return current VR mode state.
- *
- * @return {@code true} if VR mode is enabled.
- */
- public abstract boolean isInVrMode();
-
- /**
* Return {@code true} if the given package is the currently bound VrListenerService for the
* given user.
*
@@ -59,22 +52,6 @@
public abstract void setVrMode(boolean enabled, @NonNull ComponentName packageName,
int userId, @NonNull ComponentName calling);
- /**
- * Add a listener for VR mode state changes.
- * <p>
- * This listener will immediately be called with the current VR mode state.
- * </p>
- * @param listener the listener instance to add.
- */
- public abstract void registerListener(@NonNull VrStateListener listener);
-
- /**
- * Remove the listener from the current set of listeners.
- *
- * @param listener the listener to remove.
- */
- public abstract void unregisterListener(@NonNull VrStateListener listener);
-
/**
* Return NO_ERROR if the given package is installed on the device and enabled as a
* VrListenerService for the given current user, or a negative error code indicating a failure.
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index c572e76..e6e5a2d 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.vr;
+import android.Manifest;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.annotation.NonNull;
@@ -29,11 +30,15 @@
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.vr.IVrListener;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.service.vr.VrListenerService;
import android.util.ArraySet;
import android.util.Slog;
@@ -46,6 +51,7 @@
import com.android.server.utils.ManagedApplicationService.BinderChecker;
import java.lang.StringBuilder;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
@@ -75,6 +81,8 @@
public static final String TAG = "VrManagerService";
+ public static final String VR_MANAGER_BINDER_SERVICE = "vrmanager";
+
private static native void initializeNative();
private static native void setVrModeNative(boolean enabled);
@@ -84,7 +92,6 @@
// State protected by mLock
private boolean mVrModeEnabled;
- private final Set<VrStateListener> mListeners = new ArraySet<>();
private EnabledComponentsObserver mComponentObserver;
private ManagedApplicationService mCurrentVrService;
private Context mContext;
@@ -92,10 +99,37 @@
private int mCurrentVrModeUser;
private boolean mWasDefaultGranted;
private boolean mGuard;
+ private final RemoteCallbackList<IVrStateCallbacks> mRemoteCallbacks =
+ new RemoteCallbackList<>();
private final ArraySet<String> mPreviousToggledListenerSettings = new ArraySet<>();
private String mPreviousNotificationPolicyAccessPackage;
private String mPreviousManageOverlayPackage;
+ private static final int MSG_VR_STATE_CHANGE = 0;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_VR_STATE_CHANGE : {
+ boolean state = (msg.arg1 == 1);
+ int i = mRemoteCallbacks.beginBroadcast();
+ while (i > 0) {
+ i--;
+ try {
+ mRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state);
+ } catch (RemoteException e) {
+ // Noop
+ }
+ }
+ mRemoteCallbacks.finishBroadcast();
+ } break;
+ default :
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ };
+
private static final BinderChecker sBinderChecker = new BinderChecker() {
@Override
public IInterface asInterface(IBinder binder) {
@@ -125,16 +159,47 @@
}
}
+ private final IVrManager mVrManager = new IVrManager.Stub() {
+
+ @Override
+ public void registerListener(IVrStateCallbacks cb) {
+ enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback binder object is null.");
+ }
+
+ VrManagerService.this.addStateCallback(cb);
+ }
+
+ @Override
+ public void unregisterListener(IVrStateCallbacks cb) {
+ enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback binder object is null.");
+ }
+
+ VrManagerService.this.removeStateCallback(cb);
+ }
+
+ @Override
+ public boolean getVrModeState() {
+ return VrManagerService.this.getVrMode();
+ }
+
+ };
+
+ private void enforceCallerPermission(String permission) {
+ if (mContext.checkCallingOrSelfPermission(permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission " + permission);
+ }
+ }
+
/**
* Implementation of VrManagerInternal. Callable only from system services.
*/
private final class LocalService extends VrManagerInternal {
@Override
- public boolean isInVrMode() {
- return VrManagerService.this.getVrMode();
- }
-
- @Override
public void setVrMode(boolean enabled, ComponentName packageName, int userId,
ComponentName callingPackage) {
VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage);
@@ -146,16 +211,6 @@
}
@Override
- public void registerListener(VrStateListener listener) {
- VrManagerService.this.addListener(listener);
- }
-
- @Override
- public void unregisterListener(VrStateListener listener) {
- VrManagerService.this.removeListener(listener);
- }
-
- @Override
public int hasVrPackage(ComponentName packageName, int userId) {
return VrManagerService.this.hasVrPackage(packageName, userId);
}
@@ -173,6 +228,7 @@
}
publishLocalService(VrManagerInternal.class, new LocalService());
+ publishBinderService(VR_MANAGER_BINDER_SERVICE, mVrManager.asBinder());
}
@Override
@@ -551,9 +607,8 @@
* Note: Must be called while holding {@code mLock}.
*/
private void onVrModeChangedLocked() {
- for (VrStateListener l : mListeners) {
- l.onVrStateChanged(mVrModeEnabled);
- }
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_VR_STATE_CHANGE,
+ (mVrModeEnabled) ? 1 : 0, 0));
}
/**
@@ -577,9 +632,9 @@
}
}
- private boolean getVrMode() {
+ private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) {
synchronized (mLock) {
- return mVrModeEnabled;
+ return mComponentObserver.isValid(targetPackageName, userId);
}
}
@@ -593,21 +648,21 @@
}
}
- private void addListener(VrStateListener listener) {
- synchronized (mLock) {
- mListeners.add(listener);
- }
+ /*
+ * Implementation of IVrManager calls.
+ */
+
+ private void addStateCallback(IVrStateCallbacks cb) {
+ mRemoteCallbacks.register(cb);
}
- private void removeListener(VrStateListener listener) {
- synchronized (mLock) {
- mListeners.remove(listener);
- }
+ private void removeStateCallback(IVrStateCallbacks cb) {
+ mRemoteCallbacks.unregister(cb);
}
- private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) {
+ private boolean getVrMode() {
synchronized (mLock) {
- return mComponentObserver.isValid(targetPackageName, userId);
+ return mVrModeEnabled;
}
}
}
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 6052a6e..9486cfd 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -110,9 +110,7 @@
Log.e(TAG, "Found an element that is not a webview provider");
}
}
- } catch(XmlPullParserException e) {
- throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
- } catch(IOException e) {
+ } catch (XmlPullParserException | IOException e) {
throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
} finally {
if (parser != null) parser.close();
@@ -120,6 +118,11 @@
return webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
}
+ public int getFactoryPackageVersion(String packageName) throws NameNotFoundException {
+ PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_FACTORY_ONLY).versionCode;
+ }
+
/**
* Reads all signatures at the current depth (within the current provider) from the XML parser.
*/
@@ -181,15 +184,18 @@
@Override
public void uninstallAndDisablePackageForAllUsers(Context context, String packageName) {
- context.getPackageManager().deletePackage(packageName,
- new IPackageDeleteObserver.Stub() {
- public void packageDeleted(String packageName, int returnCode) {
- // Ignore returnCode since the deletion could fail, e.g. we might be trying
- // to delete a non-updated system-package (and we should still disable the
- // package)
- enablePackageForAllUsers(context, packageName, false);
+ enablePackageForAllUsers(context, packageName, false);
+ try {
+ PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ if (pm.getApplicationInfo(packageName, 0).isUpdatedSystemApp()) {
+ pm.deletePackage(packageName, new IPackageDeleteObserver.Stub() {
+ public void packageDeleted(String packageName, int returnCode) {
+ enablePackageForAllUsers(context, packageName, false);
+ }
+ }, PackageManager.DELETE_SYSTEM_APP | PackageManager.DELETE_ALL_USERS);
}
- }, PackageManager.DELETE_SYSTEM_APP | PackageManager.DELETE_ALL_USERS);
+ } catch (NameNotFoundException e) {
+ }
}
@Override
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index b5eb0a7..7bde37a 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -32,6 +32,7 @@
public interface SystemInterface {
public WebViewProviderInfo[] getWebViewPackages();
public int onWebViewProviderChanged(PackageInfo packageInfo);
+ public int getFactoryPackageVersion(String packageName) throws NameNotFoundException;
public String getUserChosenWebViewProvider(Context context);
public void updateUserSetting(Context context, String newProviderName);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 4669676..ebec445 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -50,26 +51,21 @@
public class WebViewUpdateService extends SystemService {
private static final String TAG = "WebViewUpdateService";
- private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000.
-
- // Keeps track of the number of running relro creations
- private int mNumRelroCreationsStarted = 0;
- private int mNumRelroCreationsFinished = 0;
- // Implies that we need to rerun relro creation because we are using an out-of-date package
- private boolean mWebViewPackageDirty = false;
- private boolean mAnyWebViewInstalled = false;
-
- private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
-
- // The WebView package currently in use (or the one we are preparing).
- private PackageInfo mCurrentWebViewPackage = null;
private BroadcastReceiver mWebViewUpdatedReceiver;
private SystemInterface mSystemInterface;
+ static final int PACKAGE_CHANGED = 0;
+ static final int PACKAGE_ADDED = 1;
+ static final int PACKAGE_ADDED_REPLACED = 2;
+ static final int PACKAGE_REMOVED = 3;
+
+ private WebViewUpdater mWebViewUpdater;
+
public WebViewUpdateService(Context context) {
super(context);
mSystemInterface = new SystemImpl();
+ mWebViewUpdater = new WebViewUpdater(getContext(), mSystemInterface);
}
@Override
@@ -77,76 +73,34 @@
mWebViewUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // When a package is replaced we will receive two intents, one representing
- // the removal of the old package and one representing the addition of the
- // new package.
- // In the case where we receive an intent to remove the old version of the
- // package that is being replaced we early-out here so that we don't run the
- // update-logic twice.
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
- && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
- return;
- }
-
- // Ensure that we only heed PACKAGE_CHANGED intents if they change an entire
- // package, not just a component
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) {
- if (!entirePackageChanged(intent)) {
- return;
- }
- }
-
- if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) {
- int userId =
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- handleNewUser(userId);
- return;
- }
-
- updateFallbackState(context, intent);
-
- for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
- String webviewPackage = "package:" + provider.packageName;
-
- if (webviewPackage.equals(intent.getDataString())) {
- boolean updateWebView = false;
- boolean removedOrChangedOldPackage = false;
- String oldProviderName = null;
- PackageInfo newPackage = null;
- synchronized(WebViewUpdateService.this) {
- try {
- newPackage = findPreferredWebViewPackage();
- if (mCurrentWebViewPackage != null)
- oldProviderName = mCurrentWebViewPackage.packageName;
- // Only trigger update actions if the updated package is the one
- // that will be used, or the one that was in use before the
- // update, or if we haven't seen a valid WebView package before.
- updateWebView =
- provider.packageName.equals(newPackage.packageName)
- || provider.packageName.equals(oldProviderName)
- || mCurrentWebViewPackage == null;
- // We removed the old package if we received an intent to remove
- // or replace the old package.
- removedOrChangedOldPackage =
- provider.packageName.equals(oldProviderName);
- if (updateWebView) {
- onWebViewProviderChanged(newPackage);
- }
- } catch (WebViewFactory.MissingWebViewPackageException e) {
- Slog.e(TAG, "Could not find valid WebView package to create " +
- "relro with " + e);
- }
+ switch (intent.getAction()) {
+ case Intent.ACTION_PACKAGE_REMOVED:
+ // When a package is replaced we will receive two intents, one
+ // representing the removal of the old package and one representing the
+ // addition of the new package.
+ // In the case where we receive an intent to remove the old version of
+ // the package that is being replaced we early-out here so that we don't
+ // run the update-logic twice.
+ if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return;
+ packageStateChanged(packageNameFromIntent(intent), PACKAGE_REMOVED);
+ break;
+ case Intent.ACTION_PACKAGE_CHANGED:
+ // Ensure that we only heed PACKAGE_CHANGED intents if they change an
+ // entire package, not just a component
+ if (entirePackageChanged(intent)) {
+ packageStateChanged(packageNameFromIntent(intent), PACKAGE_CHANGED);
}
- if(updateWebView && !removedOrChangedOldPackage
- && oldProviderName != null) {
- // If the provider change is the result of adding or replacing a
- // package that was not the previous provider then we must kill
- // packages dependent on the old package ourselves. The framework
- // only kills dependents of packages that are being removed.
- mSystemInterface.killPackageDependents(oldProviderName);
- }
- return;
- }
+ break;
+ case Intent.ACTION_PACKAGE_ADDED:
+ packageStateChanged(packageNameFromIntent(intent),
+ (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)
+ ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED));
+ break;
+ case Intent.ACTION_USER_ADDED:
+ int userId =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ handleNewUser(userId);
+ break;
}
}
};
@@ -168,12 +122,27 @@
publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
}
+ private void packageStateChanged(String packageName, int changedState) {
+ updateFallbackState(packageName, changedState);
+ mWebViewUpdater.packageStateChanged(packageName, changedState);
+ }
+
+ public void prepareWebViewInSystemServer() {
+ updateFallbackStateOnBoot();
+ mWebViewUpdater.prepareWebViewInSystemServer();
+ }
+
+ private static String packageNameFromIntent(Intent intent) {
+ return intent.getDataString().substring("package:".length());
+ }
+
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
for (WebViewProviderInfo provider : providers) {
if (provider.availableByDefault && !provider.isFallback) {
try {
PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
- if (isEnabledPackage(packageInfo) && isValidProvider(provider, packageInfo)) {
+ if (isEnabledPackage(packageInfo)
+ && mWebViewUpdater.isValidProvider(provider, packageInfo)) {
return true;
}
} catch (NameNotFoundException e) {
@@ -198,33 +167,37 @@
!existsValidNonFallbackProvider(webviewProviders), userId);
}
+ public void updateFallbackStateOnBoot() {
+ WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
+ updateFallbackState(webviewProviders, true);
+ }
+
/**
* Handle the enabled-state of our fallback package, i.e. if there exists some non-fallback
* package that is valid (and available by default) then disable the fallback package,
* otherwise, enable the fallback package.
*/
- void updateFallbackState(final Context context, final Intent intent) {
+ public void updateFallbackState(String changedPackage, int changedState) {
if (!mSystemInterface.isFallbackLogicEnabled()) return;
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- if (intent != null && (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)
- || intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED))) {
- // A package was changed / updated / downgraded, early out if it is not one of the
- // webview packages that are available by default.
- String changedPackage = null;
- for (WebViewProviderInfo provider : webviewProviders) {
- String webviewPackage = "package:" + provider.packageName;
- if (webviewPackage.equals(intent.getDataString())) {
- if (provider.availableByDefault) {
- changedPackage = provider.packageName;
- }
- break;
+ // A package was changed / updated / downgraded, early out if it is not one of the
+ // webview packages that are available by default.
+ boolean changedPackageAvailableByDefault = false;
+ for (WebViewProviderInfo provider : webviewProviders) {
+ if (provider.packageName.equals(changedPackage)) {
+ if (provider.availableByDefault) {
+ changedPackageAvailableByDefault = true;
}
+ break;
}
- if (changedPackage == null) return;
}
+ if (!changedPackageAvailableByDefault) return;
+ updateFallbackState(webviewProviders, false);
+ }
+ private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) {
// If there exists a valid and enabled non-fallback package - disable the fallback
// package, otherwise, enable it.
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
@@ -233,28 +206,29 @@
boolean isFallbackEnabled = false;
try {
- isFallbackEnabled =
- isEnabledPackage(mSystemInterface.getPackageInfoForProvider(fallbackProvider));
+ isFallbackEnabled = isEnabledPackage(
+ mSystemInterface.getPackageInfoForProvider(fallbackProvider));
} catch (NameNotFoundException e) {
}
if (existsValidNonFallbackProvider
// During an OTA the primary user's WebView state might differ from other users', so
// ignore the state of that user during boot.
- && (isFallbackEnabled || intent == null)) {
- mSystemInterface.uninstallAndDisablePackageForAllUsers(context,
+ && (isFallbackEnabled || isBoot)) {
+ mSystemInterface.uninstallAndDisablePackageForAllUsers(getContext(),
fallbackProvider.packageName);
} else if (!existsValidNonFallbackProvider
// During an OTA the primary user's WebView state might differ from other users', so
// ignore the state of that user during boot.
- && (!isFallbackEnabled || intent==null)) {
+ && (!isFallbackEnabled || isBoot)) {
// Enable the fallback package for all users.
- mSystemInterface.enablePackageForAllUsers(context, fallbackProvider.packageName, true);
+ mSystemInterface.enablePackageForAllUsers(getContext(),
+ fallbackProvider.packageName, true);
}
}
/**
- * Returns the only fallback provider, or null if there is none.
+ * Returns the only fallback provider in the set of given packages, or null if there is none.
*/
private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
for (WebViewProviderInfo provider : webviewPackages) {
@@ -275,180 +249,361 @@
}
/**
- * Perform any WebView loading preparations that must happen at boot from the system server,
- * after the package manager has started or after an update to the webview is installed.
- * This must be called in the system server.
- * Currently, this means spawning the child processes which will create the relro files.
+ * Class that decides what WebView implementation to use and prepares that implementation for
+ * use.
*/
- public void prepareWebViewInSystemServer() {
- updateFallbackState(getContext(), null);
- try {
- synchronized(this) {
- mCurrentWebViewPackage = findPreferredWebViewPackage();
- onWebViewProviderChanged(mCurrentWebViewPackage);
- }
- } catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ private static class WebViewUpdater {
+ private Context mContext;
+ private SystemInterface mSystemInterface;
+ private int mMinimumVersionCode = -1;
+
+ public WebViewUpdater(Context context, SystemInterface systemInterface) {
+ mContext = context;
+ mSystemInterface = systemInterface;
}
- }
+ private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000.
- /**
- * Change WebView provider and provider setting and kill packages using the old provider.
- * Return the new provider (in case we are in the middle of creating relro files this new
- * provider will not be in use directly, but will when the relros are done).
- */
- private String changeProviderAndSetting(String newProviderName) {
- PackageInfo oldPackage = null;
- PackageInfo newPackage = null;
- synchronized(this) {
- oldPackage = mCurrentWebViewPackage;
- mSystemInterface.updateUserSetting(getContext(), newProviderName);
+ // Keeps track of the number of running relro creations
+ private int mNumRelroCreationsStarted = 0;
+ private int mNumRelroCreationsFinished = 0;
+ // Implies that we need to rerun relro creation because we are using an out-of-date package
+ private boolean mWebViewPackageDirty = false;
+ private boolean mAnyWebViewInstalled = false;
- try {
- newPackage = findPreferredWebViewPackage();
- if (oldPackage != null && newPackage.packageName.equals(oldPackage.packageName)) {
- // If we don't perform the user change, revert the settings change.
- mSystemInterface.updateUserSetting(getContext(), newPackage.packageName);
- return newPackage.packageName;
+ private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+ // The WebView package currently in use (or the one we are preparing).
+ private PackageInfo mCurrentWebViewPackage = null;
+
+ private Object mLock = new Object();
+
+ public void packageStateChanged(String packageName, int changedState) {
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ String webviewPackage = provider.packageName;
+
+ if (webviewPackage.equals(packageName)) {
+ boolean updateWebView = false;
+ boolean removedOrChangedOldPackage = false;
+ String oldProviderName = null;
+ PackageInfo newPackage = null;
+ synchronized(mLock) {
+ try {
+ newPackage = findPreferredWebViewPackage();
+ if (mCurrentWebViewPackage != null)
+ oldProviderName = mCurrentWebViewPackage.packageName;
+ // Only trigger update actions if the updated package is the one
+ // that will be used, or the one that was in use before the
+ // update, or if we haven't seen a valid WebView package before.
+ updateWebView =
+ provider.packageName.equals(newPackage.packageName)
+ || provider.packageName.equals(oldProviderName)
+ || mCurrentWebViewPackage == null;
+ // We removed the old package if we received an intent to remove
+ // or replace the old package.
+ removedOrChangedOldPackage =
+ provider.packageName.equals(oldProviderName);
+ if (updateWebView) {
+ onWebViewProviderChanged(newPackage);
+ }
+ } catch (WebViewFactory.MissingWebViewPackageException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create " +
+ "relro with " + e);
+ }
+ }
+ if(updateWebView && !removedOrChangedOldPackage
+ && oldProviderName != null) {
+ // If the provider change is the result of adding or replacing a
+ // package that was not the previous provider then we must kill
+ // packages dependent on the old package ourselves. The framework
+ // only kills dependents of packages that are being removed.
+ mSystemInterface.killPackageDependents(oldProviderName);
+ }
+ return;
}
- } catch (WebViewFactory.MissingWebViewPackageException e) {
- Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView package "
- + e);
- // If we don't perform the user change but don't have an installed WebView package,
- // we will have changed the setting and it will be used when a package is available.
- return newProviderName;
}
- onWebViewProviderChanged(newPackage);
}
- // Kill apps using the old provider
- if (oldPackage != null) {
- mSystemInterface.killPackageDependents(oldPackage.packageName);
+
+ public void prepareWebViewInSystemServer() {
+ try {
+ synchronized(mLock) {
+ mCurrentWebViewPackage = findPreferredWebViewPackage();
+ onWebViewProviderChanged(mCurrentWebViewPackage);
+ }
+ } catch (Throwable t) {
+ // Log and discard errors at this stage as we must not crash the system server.
+ Slog.e(TAG, "error preparing webview provider from system server", t);
+ }
}
- return newPackage.packageName;
- }
- /**
- * This is called when we change WebView provider, either when the current provider is updated
- * or a new provider is chosen / takes precedence.
- */
- private void onWebViewProviderChanged(PackageInfo newPackage) {
- synchronized(this) {
- mAnyWebViewInstalled = true;
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- mCurrentWebViewPackage = newPackage;
- mSystemInterface.updateUserSetting(getContext(), newPackage.packageName);
+ /**
+ * Change WebView provider and provider setting and kill packages using the old provider.
+ * Return the new provider (in case we are in the middle of creating relro files this new
+ * provider will not be in use directly, but will when the relros are done).
+ */
+ public String changeProviderAndSetting(String newProviderName) {
+ PackageInfo oldPackage = null;
+ PackageInfo newPackage = null;
+ synchronized(mLock) {
+ oldPackage = mCurrentWebViewPackage;
+ mSystemInterface.updateUserSetting(mContext, newProviderName);
- // The relro creations might 'finish' (not start at all) before
- // WebViewFactory.onWebViewProviderChanged which means we might not know the number
- // of started creations before they finish.
- mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
- mNumRelroCreationsFinished = 0;
- mNumRelroCreationsStarted = mSystemInterface.onWebViewProviderChanged(newPackage);
- // If the relro creations finish before we know the number of started creations we
- // will have to do any cleanup/notifying here.
+ try {
+ newPackage = findPreferredWebViewPackage();
+ if (oldPackage != null
+ && newPackage.packageName.equals(oldPackage.packageName)) {
+ // If we don't perform the user change, revert the settings change.
+ mSystemInterface.updateUserSetting(mContext, newPackage.packageName);
+ return newPackage.packageName;
+ }
+ } catch (WebViewFactory.MissingWebViewPackageException e) {
+ Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " +
+ "package " + e);
+ // If we don't perform the user change but don't have an installed WebView
+ // package, we will have changed the setting and it will be used when a package
+ // is available.
+ return newProviderName;
+ }
+ onWebViewProviderChanged(newPackage);
+ }
+ // Kill apps using the old provider
+ if (oldPackage != null) {
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
+ }
+ return newPackage.packageName;
+ }
+
+ /**
+ * This is called when we change WebView provider, either when the current provider is
+ * updated or a new provider is chosen / takes precedence.
+ */
+ private void onWebViewProviderChanged(PackageInfo newPackage) {
+ synchronized(mLock) {
+ mAnyWebViewInstalled = true;
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ mCurrentWebViewPackage = newPackage;
+ mSystemInterface.updateUserSetting(mContext, newPackage.packageName);
+
+ // The relro creations might 'finish' (not start at all) before
+ // WebViewFactory.onWebViewProviderChanged which means we might not know the
+ // number of started creations before they finish.
+ mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+ mNumRelroCreationsFinished = 0;
+ mNumRelroCreationsStarted =
+ mSystemInterface.onWebViewProviderChanged(newPackage);
+ // If the relro creations finish before we know the number of started creations
+ // we will have to do any cleanup/notifying here.
+ checkIfRelrosDoneLocked();
+ } else {
+ mWebViewPackageDirty = true;
+ }
+ }
+ }
+
+ private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+ List<ProviderAndPackageInfo> providers = new ArrayList<>();
+ for(int n = 0; n < allProviders.length; n++) {
+ try {
+ PackageInfo packageInfo =
+ mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+ if (isValidProvider(allProviders[n], packageInfo)) {
+ providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+ }
+ } catch (NameNotFoundException e) {
+ // Don't add non-existent packages
+ }
+ }
+ return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+ }
+
+ /**
+ * Fetch only the currently valid WebView packages.
+ **/
+ public WebViewProviderInfo[] getValidWebViewPackages() {
+ ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+ WebViewProviderInfo[] providers =
+ new WebViewProviderInfo[providersAndPackageInfos.length];
+ for(int n = 0; n < providersAndPackageInfos.length; n++) {
+ providers[n] = providersAndPackageInfos[n].provider;
+ }
+ return providers;
+ }
+
+
+ private class ProviderAndPackageInfo {
+ public final WebViewProviderInfo provider;
+ public final PackageInfo packageInfo;
+
+ public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+ this.provider = provider;
+ this.packageInfo = packageInfo;
+ }
+ }
+
+ /**
+ * Returns either the package info of the WebView provider determined in the following way:
+ * If the user has chosen a provider then use that if it is valid,
+ * otherwise use the first package in the webview priority list that is valid.
+ *
+ */
+ private PackageInfo findPreferredWebViewPackage() {
+ ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+ String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+ // If the user has chosen provider, use that
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.packageName.equals(userChosenProvider)
+ && isEnabledPackage(providerAndPackage.packageInfo)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+
+ // User did not choose, or the choice failed; use the most stable provider that is
+ // enabled and available by default (not through user choice).
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.availableByDefault
+ && isEnabledPackage(providerAndPackage.packageInfo)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+
+ // Could not find any enabled package either, use the most stable provider.
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ return providerAndPackage.packageInfo;
+ }
+
+ mAnyWebViewInstalled = false;
+ throw new WebViewFactory.MissingWebViewPackageException(
+ "Could not find a loadable WebView package");
+ }
+
+ public void notifyRelroCreationCompleted() {
+ synchronized (mLock) {
+ mNumRelroCreationsFinished++;
checkIfRelrosDoneLocked();
- } else {
- mWebViewPackageDirty = true;
}
}
- }
- private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
- WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
- List<ProviderAndPackageInfo> providers = new ArrayList<>();
- for(int n = 0; n < allProviders.length; n++) {
- try {
- PackageInfo packageInfo =
- mSystemInterface.getPackageInfoForProvider(allProviders[n]);
- if (isValidProvider(allProviders[n], packageInfo)) {
- providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+ public WebViewProviderResponse waitForAndGetProvider() {
+ PackageInfo webViewPackage = null;
+ final long NS_PER_MS = 1000000;
+ final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+ boolean webViewReady = false;
+ int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+ synchronized (mLock) {
+ webViewReady = webViewIsReadyLocked();
+ while (!webViewReady) {
+ final long timeNowMs = System.nanoTime() / NS_PER_MS;
+ if (timeNowMs >= timeoutTimeMs) break;
+ try {
+ mLock.wait(timeoutTimeMs - timeNowMs);
+ } catch (InterruptedException e) {}
+ webViewReady = webViewIsReadyLocked();
}
- } catch (NameNotFoundException e) {
- // Don't add non-existent packages
+ // Make sure we return the provider that was used to create the relro file
+ webViewPackage = mCurrentWebViewPackage;
+ if (webViewReady) {
+ } else if (!mAnyWebViewInstalled) {
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ } else {
+ // Either the current relro creation isn't done yet, or the new relro creatioin
+ // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+ }
}
+ if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+ return new WebViewProviderResponse(webViewPackage, webViewStatus);
}
- return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
- }
- /**
- * Fetch only the currently valid WebView packages.
- **/
- private WebViewProviderInfo[] getValidWebViewPackages() {
- ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
- WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length];
- for(int n = 0; n < providersAndPackageInfos.length; n++) {
- providers[n] = providersAndPackageInfos[n].provider;
- }
- return providers;
- }
-
- private class ProviderAndPackageInfo {
- public final WebViewProviderInfo provider;
- public final PackageInfo packageInfo;
-
- public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
- this.provider = provider;
- this.packageInfo = packageInfo;
- }
- }
-
- /**
- * Returns either the package info of the WebView provider determined in the following way:
- * If the user has chosen a provider then use that if it is valid,
- * otherwise use the first package in the webview priority list that is valid.
- *
- * @hide
- */
- private PackageInfo findPreferredWebViewPackage() {
- ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
- String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(getContext());
-
- // If the user has chosen provider, use that
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ public String getCurrentWebViewPackageName() {
+ synchronized(mLock) {
+ if (mCurrentWebViewPackage == null)
+ return null;
+ return mCurrentWebViewPackage.packageName;
}
}
- // User did not choose, or the choice failed; use the most stable provider that is
- // enabled and available by default (not through user choice).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ /**
+ * Returns whether WebView is ready and is not going to go through its preparation phase
+ * again directly.
+ */
+ private boolean webViewIsReadyLocked() {
+ return !mWebViewPackageDirty
+ && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+ // The current package might be replaced though we haven't received an intent
+ // declaring this yet, the following flag makes anyone loading WebView to wait in
+ // this case.
+ && mAnyWebViewInstalled;
+ }
+
+ private void checkIfRelrosDoneLocked() {
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ if (mWebViewPackageDirty) {
+ mWebViewPackageDirty = false;
+ // If we have changed provider since we started the relro creation we need to
+ // redo the whole process using the new package instead.
+ PackageInfo newPackage = findPreferredWebViewPackage();
+ onWebViewProviderChanged(newPackage);
+ } else {
+ mLock.notifyAll();
+ }
}
}
- // Could not find any enabled package either, use the most stable provider.
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- return providerAndPackage.packageInfo;
+ /**
+ * Returns whether this provider is valid for use as a WebView provider.
+ */
+ public boolean isValidProvider(WebViewProviderInfo configInfo,
+ PackageInfo packageInfo) {
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
+ && packageInfo.versionCode < getMinimumVersionCode()
+ && !mSystemInterface.systemIsDebuggable()) {
+ // Non-system package webview providers may be downgraded arbitrarily low, prevent
+ // that by enforcing minimum version code. This check is only enforced for user
+ // builds.
+ return false;
+ }
+ if (providerHasValidSignature(configInfo, packageInfo, mSystemInterface) &&
+ WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
+ return true;
+ }
+ return false;
}
- mAnyWebViewInstalled = false;
- throw new WebViewFactory.MissingWebViewPackageException(
- "Could not find a loadable WebView package");
- }
+ /**
+ * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+ * of all available-by-default and non-fallback WebView provider packages. If there is no
+ * such WebView provider package on the system, then return -1, which means all positive
+ * versionCode WebView packages are accepted.
+ */
+ private int getMinimumVersionCode() {
+ if (mMinimumVersionCode > 0) {
+ return mMinimumVersionCode;
+ }
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ if (provider.availableByDefault && !provider.isFallback) {
+ try {
+ int versionCode =
+ mSystemInterface.getFactoryPackageVersion(provider.packageName);
+ if (mMinimumVersionCode < 0 || versionCode < mMinimumVersionCode) {
+ mMinimumVersionCode = versionCode;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Safe to ignore.
+ }
+ }
+ }
- /**
- * Returns whether this provider is valid for use as a WebView provider.
- */
- public boolean isValidProvider(WebViewProviderInfo configInfo,
- PackageInfo packageInfo) {
- if (providerHasValidSignature(configInfo, packageInfo) &&
- WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
- return true;
+ return mMinimumVersionCode;
}
- return false;
}
- private boolean providerHasValidSignature(WebViewProviderInfo provider,
- PackageInfo packageInfo) {
- if (mSystemInterface.systemIsDebuggable()) {
+ private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+ PackageInfo packageInfo, SystemInterface systemInterface) {
+ if (systemInterface.systemIsDebuggable()) {
return true;
}
Signature[] packageSignatures;
@@ -475,7 +630,7 @@
* Returns whether the given package is enabled.
* This state can be changed by the user from Settings->Apps
*/
- public boolean isEnabledPackage(PackageInfo packageInfo) {
+ private static boolean isEnabledPackage(PackageInfo packageInfo) {
return packageInfo.applicationInfo.enabled;
}
@@ -491,32 +646,6 @@
intent.getDataString().substring("package:".length()));
}
- /**
- * Returns whether WebView is ready and is not going to go through its preparation phase again
- * directly.
- */
- private boolean webViewIsReadyLocked() {
- return !mWebViewPackageDirty
- && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
- // The current package might be replaced though we haven't received an intent declaring
- // this yet, the following flag makes anyone loading WebView to wait in this case.
- && mAnyWebViewInstalled;
- }
-
- private void checkIfRelrosDoneLocked() {
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- if (mWebViewPackageDirty) {
- mWebViewPackageDirty = false;
- // If we have changed provider since we started the relro creation we need to
- // redo the whole process using the new package instead.
- PackageInfo newPackage = findPreferredWebViewPackage();
- onWebViewProviderChanged(newPackage);
- } else {
- this.notifyAll();
- }
- }
- }
-
private class BinderService extends IWebViewUpdateService.Stub {
@Override
@@ -544,10 +673,7 @@
long callingId = Binder.clearCallingIdentity();
try {
- synchronized (WebViewUpdateService.this) {
- mNumRelroCreationsFinished++;
- checkIfRelrosDoneLocked();
- }
+ WebViewUpdateService.this.mWebViewUpdater.notifyRelroCreationCompleted();
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -567,34 +693,7 @@
throw new IllegalStateException("Cannot create a WebView from the SystemServer");
}
- PackageInfo webViewPackage = null;
- final long NS_PER_MS = 1000000;
- final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
- boolean webViewReady = false;
- int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
- synchronized (WebViewUpdateService.this) {
- webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
- while (!webViewReady) {
- final long timeNowMs = System.nanoTime() / NS_PER_MS;
- if (timeNowMs >= timeoutTimeMs) break;
- try {
- WebViewUpdateService.this.wait(timeoutTimeMs - timeNowMs);
- } catch (InterruptedException e) {}
- webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
- }
- // Make sure we return the provider that was used to create the relro file
- webViewPackage = WebViewUpdateService.this.mCurrentWebViewPackage;
- if (webViewReady) {
- } else if (!mAnyWebViewInstalled) {
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
- } else {
- // Either the current relro creation isn't done yet, or the new relro creatioin
- // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
- }
- }
- if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
- return new WebViewProviderResponse(webViewPackage, webViewStatus);
+ return WebViewUpdateService.this.mWebViewUpdater.waitForAndGetProvider();
}
/**
@@ -615,7 +714,8 @@
long callingId = Binder.clearCallingIdentity();
try {
- return WebViewUpdateService.this.changeProviderAndSetting(newProvider);
+ return WebViewUpdateService.this.mWebViewUpdater.changeProviderAndSetting(
+ newProvider);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -623,7 +723,7 @@
@Override // Binder call
public WebViewProviderInfo[] getValidWebViewPackages() {
- return WebViewUpdateService.this.getValidWebViewPackages();
+ return WebViewUpdateService.this.mWebViewUpdater.getValidWebViewPackages();
}
@Override // Binder call
@@ -633,11 +733,7 @@
@Override // Binder call
public String getCurrentWebViewPackageName() {
- synchronized(WebViewUpdateService.this) {
- if (WebViewUpdateService.this.mCurrentWebViewPackage == null)
- return null;
- return WebViewUpdateService.this.mCurrentWebViewPackage.packageName;
- }
+ return WebViewUpdateService.this.mWebViewUpdater.getCurrentWebViewPackageName();
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d684278..11b01cc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -881,7 +881,7 @@
* when a thumbnail is specified with the pending animation override.
*/
Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
- Bitmap thumbnailHeader, final int taskId, int orientation) {
+ Bitmap thumbnailHeader, final int taskId, int uiMode, int orientation) {
Animation a;
final int thumbWidthI = thumbnailHeader.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -896,7 +896,7 @@
final float toY;
final float pivotX;
final float pivotY;
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) {
fromX = mTmpRect.left;
fromY = mTmpRect.top;
@@ -1028,7 +1028,7 @@
* activity that is leaving, and the activity that is entering.
*/
Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
- int orientation, int transit, Rect containingFrame, Rect contentInsets,
+ int uiMode, int orientation, int transit, Rect containingFrame, Rect contentInsets,
@Nullable Rect surfaceInsets, boolean freeform, int taskId) {
Animation a;
final int appWidth = containingFrame.width();
@@ -1067,8 +1067,7 @@
mTmpFromClipRect.inset(contentInsets);
mNextAppTransitionInsets.set(contentInsets);
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- // We scale the width and clip to the top/left square
+ if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) {
// We scale the width and clip to the top/left square
float scale = thumbWidth /
(appWidth - contentInsets.left - contentInsets.right);
@@ -1401,7 +1400,7 @@
* to the recents thumbnail and hence need to account for the surface being
* bigger.
*/
- Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+ Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode,
int orientation, Rect frame, Rect displayFrame, Rect insets,
@Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
int taskId) {
@@ -1481,7 +1480,7 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
a = createAspectScaledThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), orientation, transit, frame,
+ getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
insets, surfaceInsets, freeform, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
@@ -1922,4 +1921,11 @@
}
return prepared;
}
+
+ /**
+ * @return whether the specified {@param uiMode} is the TV mode.
+ */
+ private boolean isTvUiMode(int uiMode) {
+ return (uiMode & Configuration.UI_MODE_TYPE_TELEVISION) > 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6f7e64f..6ac71c7 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -76,8 +76,8 @@
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
- private final int mDividerWindowWidth;
- private final int mDividerInsets;
+ private int mDividerWindowWidth;
+ private int mDividerInsets;
private boolean mResizing;
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
@@ -105,14 +105,23 @@
mService = service;
mDisplayContent = displayContent;
final Context context = service.mContext;
- mDividerWindowWidth = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerInsets = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
"DockedStackDim");
mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_slow_in);
+ loadDimens();
+ }
+
+ private void loadDimens() {
+ final Context context = mService.mContext;
+ mDividerWindowWidth = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ mDividerInsets = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ }
+
+ void onConfigurationChanged() {
+ loadDimens();
}
boolean isResizing() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index eea0ca0..8a9ace7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -595,6 +595,12 @@
if (win.mHasSurface && !resizingWindows.contains(win)) {
if (DEBUG_RESIZE) Slog.d(TAG, "resizeWindows: Resizing " + win);
resizingWindows.add(win);
+
+ // If we are not drag resizing, force recreating of a new surface so updating
+ // the content and positioning that surface will be in sync.
+ if (!win.computeDragResizing()) {
+ win.mResizedWhileNotDragResizing = true;
+ }
}
if (win.isGoneForLayoutLw()) {
win.mResizedWhileGone = true;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 0225c9b..3430ac9 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -37,8 +37,8 @@
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
@@ -89,6 +89,9 @@
// Device rotation as of the last time {@link #mBounds} was set.
int mRotation;
+ /** Density as of last time {@link #mBounds} was set. */
+ int mDensity;
+
/** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
DimLayer mAnimationBackgroundSurface;
@@ -250,9 +253,11 @@
private boolean setBounds(Rect bounds) {
boolean oldFullscreen = mFullscreen;
int rotation = Surface.ROTATION_0;
+ int density = DENSITY_DPI_UNDEFINED;
if (mDisplayContent != null) {
mDisplayContent.getLogicalDisplayRect(mTmpRect);
rotation = mDisplayContent.getDisplayInfo().rotation;
+ density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
mFullscreen = bounds == null;
if (mFullscreen) {
bounds = mTmpRect;
@@ -274,6 +279,7 @@
mBounds.set(bounds);
mRotation = rotation;
+ mDensity = density;
updateAdjustedBounds();
@@ -343,20 +349,21 @@
mTmpRect2.set(mBounds);
final int newRotation = mDisplayContent.getDisplayInfo().rotation;
- if (mRotation == newRotation) {
+ final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
+ if (mRotation == newRotation && mDensity == newDensity) {
setBounds(mTmpRect2);
} else {
mLastUpdateDisplayInfoRotation = newRotation;
- updateBoundsAfterRotation(true);
+ updateBoundsAfterConfigChange(true);
}
}
boolean onConfigurationChanged() {
mLastConfigChangedRotation = getDisplayInfo().rotation;
- return updateBoundsAfterRotation(false);
+ return updateBoundsAfterConfigChange(false);
}
- boolean updateBoundsAfterRotation(boolean scheduleResize) {
+ boolean updateBoundsAfterConfigChange(boolean scheduleResize) {
if (mLastConfigChangedRotation != mLastUpdateDisplayInfoRotation) {
// We wait for the rotation values after configuration change and display info. update
// to be equal before updating the bounds due to rotation change otherwise things might
@@ -365,8 +372,9 @@
}
final int newRotation = getDisplayInfo().rotation;
+ final int newDensity = getDisplayInfo().logicalDensityDpi;
- if (mRotation == newRotation) {
+ if (mRotation == newRotation && mDensity == newDensity) {
// Nothing to do here if the rotation didn't change
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 25da773..2af324d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2586,7 +2586,7 @@
try {
- win.applyGravityAndUpdateFrame();
+ win.applyGravityAndUpdateFrame(win.mContainingFrame, win.mDisplayFrame);
win.mWinAnimator.computeShownFrameLocked();
win.mWinAnimator.setSurfaceBoundariesLocked(false);
@@ -2935,8 +2935,9 @@
// If we're starting a drag-resize, we'll be changing the surface size as well as
// notifying the client to render to with an offset from the surface's top-left.
- if (win.isDragResizeChanged()) {
+ if (win.isDragResizeChanged() || win.mResizedWhileNotDragResizing) {
win.setDragResizing();
+ win.mResizedWhileNotDragResizing = false;
// We can only change top level windows to the full-screen surface when
// resizing (as we only have one full-screen surface). So there is no need
// to preserve and destroy windows which are attached to another, they
@@ -3058,7 +3059,7 @@
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+ " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode,
mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
isVoiceInteraction, freeform, atoken.mTask.mTaskId);
if (a != null) {
@@ -3627,6 +3628,8 @@
}
private int[] onConfigurationChanged() {
+ mPolicy.onConfigurationChanged();
+ getDefaultDisplayContentLocked().getDockedDividerController().onConfigurationChanged();
mChangedStackList.clear();
for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) {
final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
@@ -9021,7 +9024,8 @@
|| winAnimator.mSurfaceResized
|| w.mOutsetsChanged
|| configChanged
- || dragResizingChanged) {
+ || dragResizingChanged
+ || w.mResizedWhileNotDragResizing) {
if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
Slog.v(TAG_WM, "Resize reasons for w=" + w + ": "
+ " contentInsetsChanged=" + w.mContentInsetsChanged
@@ -9055,7 +9059,8 @@
// the display until this window has been redrawn; to do that,
// we need to go through the process of getting informed by the
// application when it has finished drawing.
- if (w.mOrientationChanging || dragResizingChanged) {
+ if (w.mOrientationChanging || dragResizingChanged
+ || w.mResizedWhileNotDragResizing) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
+ ", mDrawState=DRAW_PENDING in " + w
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c62292d..ddfc022 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -328,6 +328,8 @@
*/
final Rect mInsetFrame = new Rect();
+ private static final Rect sTmpRect = new Rect();
+
boolean mContentChanged;
// If a window showing a wallpaper: the requested offset for the
@@ -466,6 +468,13 @@
*/
boolean mResizedWhileGone = false;
+ /**
+ * Indicates whether we got resized but drag resizing flag was false. In this case, we also
+ * need to recreate the surface and defer surface bound updates in order to make sure the
+ * buffer contents and the positioning/size stay in sync.
+ */
+ boolean mResizedWhileNotDragResizing;
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
@@ -629,6 +638,20 @@
return mAttrs.packageName;
}
+ /**
+ * Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
+ * from {@param frame}. In other words, it applies the insets that would result if
+ * {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
+ * {@param insetFrame}.
+ */
+ private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame) {
+ final int left = Math.max(0, insetFrame.left - layoutFrame.left);
+ final int top = Math.max(0, insetFrame.top - layoutFrame.top);
+ final int right = Math.max(0, layoutFrame.right - insetFrame.right);
+ final int bottom = Math.max(0, layoutFrame.bottom - insetFrame.bottom);
+ frame.inset(left, top, right, bottom);
+ }
+
@Override
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
Rect osf) {
@@ -654,11 +677,25 @@
task.getTempInsetBounds(mInsetFrame);
}
+ // Denotes the actual frame used to calculate the insets and to perform the layout. When
+ // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
+ // insets temporarily. By the notion of a task having a different layout frame, we can
+ // achieve that while still moving the task around.
+ final Rect layoutContainingFrame;
+ final Rect layoutDisplayFrame;
+
+ // The offset from the layout containing frame to the actual containing frame.
+ final int layoutXDiff;
+ final int layoutYDiff;
if (mInsetFrame.isEmpty() && (fullscreenTask
|| layoutInParentFrame())) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(pf);
mDisplayFrame.set(df);
+ layoutDisplayFrame = df;
+ layoutContainingFrame = pf;
+ layoutXDiff = 0;
+ layoutYDiff = 0;
} else {
task.getBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
@@ -693,6 +730,14 @@
}
}
mDisplayFrame.set(mContainingFrame);
+ layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
+ layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
+ layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
+ subtractInsets(mDisplayFrame, layoutContainingFrame, df);
+ subtractInsets(mContainingFrame, layoutContainingFrame, pf);
+ subtractInsets(mInsetFrame, layoutContainingFrame, pf);
+ layoutDisplayFrame = df;
+ layoutDisplayFrame.intersect(layoutContainingFrame);
}
final int pw = mContainingFrame.width();
@@ -723,7 +768,11 @@
final int fw = mFrame.width();
final int fh = mFrame.height();
- applyGravityAndUpdateFrame();
+ applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
+
+ // Offset the actual frame by the amount layout frame is off.
+ mFrame.offset(-layoutXDiff, -layoutYDiff);
+ mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
// Calculate the outsets before the content frame gets shrinked to the window frame.
if (hasOutsets) {
@@ -735,12 +784,6 @@
mOutsets.set(0, 0, 0, 0);
}
- // Denotes the actual frame used to calculate the insets. When resizing in docked mode,
- // we'd like to freeze the layout, so we also need to freeze the insets temporarily. By the
- // notion of a task having a different inset frame, we can achieve that while still moving
- // the task around.
- final Rect frame = !mInsetFrame.isEmpty() ? mInsetFrame : mFrame;
-
// Make sure the content and visible frames are inside of the
// final window frame.
if (windowsAreFloating && !mFrame.isEmpty()) {
@@ -769,29 +812,29 @@
mMovedByResize = true;
}
} else {
- mContentFrame.set(Math.max(mContentFrame.left, frame.left),
- Math.max(mContentFrame.top, frame.top),
- Math.min(mContentFrame.right, frame.right),
- Math.min(mContentFrame.bottom, frame.bottom));
+ mContentFrame.set(Math.max(mContentFrame.left, layoutContainingFrame.left),
+ Math.max(mContentFrame.top, layoutContainingFrame.top),
+ Math.min(mContentFrame.right, layoutContainingFrame.right),
+ Math.min(mContentFrame.bottom, layoutContainingFrame.bottom));
- mVisibleFrame.set(Math.max(mVisibleFrame.left, frame.left),
- Math.max(mVisibleFrame.top, frame.top),
- Math.min(mVisibleFrame.right, frame.right),
- Math.min(mVisibleFrame.bottom, frame.bottom));
+ mVisibleFrame.set(Math.max(mVisibleFrame.left, layoutContainingFrame.left),
+ Math.max(mVisibleFrame.top, layoutContainingFrame.top),
+ Math.min(mVisibleFrame.right, layoutContainingFrame.right),
+ Math.min(mVisibleFrame.bottom, layoutContainingFrame.bottom));
- mStableFrame.set(Math.max(mStableFrame.left, frame.left),
- Math.max(mStableFrame.top, frame.top),
- Math.min(mStableFrame.right, frame.right),
- Math.min(mStableFrame.bottom, frame.bottom));
+ mStableFrame.set(Math.max(mStableFrame.left, layoutContainingFrame.left),
+ Math.max(mStableFrame.top, layoutContainingFrame.top),
+ Math.min(mStableFrame.right, layoutContainingFrame.right),
+ Math.min(mStableFrame.bottom, layoutContainingFrame.bottom));
}
if (fullscreenTask && !windowsAreFloating) {
// Windows that are not fullscreen can be positioned outside of the display frame,
// but that is not a reason to provide them with overscan insets.
- mOverscanInsets.set(Math.max(mOverscanFrame.left - frame.left, 0),
- Math.max(mOverscanFrame.top - frame.top, 0),
- Math.max(frame.right - mOverscanFrame.right, 0),
- Math.max(frame.bottom - mOverscanFrame.bottom, 0));
+ mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
+ Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
+ Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
+ Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
}
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
@@ -812,45 +855,32 @@
// non-fullscreen mode.
boolean overrideRightInset = !fullscreenTask && mFrame.right > mTmpRect.right;
boolean overrideBottomInset = !fullscreenTask && mFrame.bottom > mTmpRect.bottom;
- mContentInsets.set(mContentFrame.left - frame.left,
- mContentFrame.top - frame.top,
+ mContentInsets.set(mContentFrame.left - layoutContainingFrame.left,
+ mContentFrame.top - layoutContainingFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
- : frame.right - mContentFrame.right,
+ : layoutContainingFrame.right - mContentFrame.right,
overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
- : frame.bottom - mContentFrame.bottom);
+ : layoutContainingFrame.bottom - mContentFrame.bottom);
- mVisibleInsets.set(mVisibleFrame.left - frame.left,
- mVisibleFrame.top - frame.top,
+ mVisibleInsets.set(mVisibleFrame.left - layoutContainingFrame.left,
+ mVisibleFrame.top - layoutContainingFrame.top,
overrideRightInset ? mTmpRect.right - mVisibleFrame.right
- : frame.right - mVisibleFrame.right,
+ : layoutContainingFrame.right - mVisibleFrame.right,
overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
- : frame.bottom - mVisibleFrame.bottom);
+ : layoutContainingFrame.bottom - mVisibleFrame.bottom);
- mStableInsets.set(Math.max(mStableFrame.left - frame.left, 0),
- Math.max(mStableFrame.top - frame.top, 0),
+ mStableInsets.set(Math.max(mStableFrame.left - layoutContainingFrame.left, 0),
+ Math.max(mStableFrame.top - layoutContainingFrame.top, 0),
overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
- : Math.max(frame.right - mStableFrame.right, 0),
+ : Math.max(layoutContainingFrame.right - mStableFrame.right, 0),
overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
- : Math.max(frame.bottom - mStableFrame.bottom, 0));
+ : Math.max(layoutContainingFrame.bottom - mStableFrame.bottom, 0));
}
- if (!mInsetFrame.isEmpty()) {
- mContentFrame.set(mFrame);
- mContentFrame.top += mContentInsets.top;
- mContentFrame.bottom -= mContentInsets.bottom;
- mContentFrame.left += mContentInsets.left;
- mContentFrame.right -= mContentInsets.right;
- mVisibleFrame.set(mFrame);
- mVisibleFrame.top += mVisibleInsets.top;
- mVisibleFrame.bottom -= mVisibleInsets.bottom;
- mVisibleFrame.left += mVisibleInsets.left;
- mVisibleFrame.right -= mVisibleInsets.right;
- mStableFrame.set(mFrame);
- mStableFrame.top += mStableInsets.top;
- mStableFrame.bottom -= mStableInsets.bottom;
- mStableFrame.left += mStableInsets.left;
- mStableFrame.right -= mStableInsets.right;
- }
+ mContentFrame.offset(-layoutXDiff, -layoutYDiff);
+ mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
+ mStableFrame.offset(-layoutXDiff, -layoutYDiff);
+
mCompatFrame.set(mFrame);
if (mEnforceSizeCompat) {
// If there is a size compatibility scale being applied to the
@@ -2549,9 +2579,9 @@
}
}
- void applyGravityAndUpdateFrame() {
- final int pw = mContainingFrame.width();
- final int ph = mContainingFrame.height();
+ void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
+ final int pw = containingFrame.width();
+ final int ph = containingFrame.height();
final Task task = getTask();
final boolean nonFullscreenTask = isInMultiWindowMode();
final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
@@ -2606,13 +2636,13 @@
}
// Set mFrame
- Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
+ Gravity.apply(mAttrs.gravity, w, h, containingFrame,
(int) (x + mAttrs.horizontalMargin * pw),
(int) (y + mAttrs.verticalMargin * ph), mFrame);
// Now make sure the window fits in the overall display frame.
if (fitToDisplay) {
- Gravity.applyDisplay(mAttrs.gravity, mDisplayFrame, mFrame);
+ Gravity.applyDisplay(mAttrs.gravity, displayFrame, mFrame);
}
// We need to make sure we update the CompatFrame as it is used for
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 8fd8bc0..34452ee 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1318,6 +1318,11 @@
final WindowState w = mWin;
final Task task = w.getTask();
+ // We got resized, so block all updates until we got the new surface.
+ if (w.mResizedWhileNotDragResizing) {
+ return;
+ }
+
mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
calculateSurfaceBounds(w, w.getAttrs());
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index eda2f39..04aa735 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1582,7 +1582,8 @@
// synchronize its thumbnail surface with the surface for the
// open/close animation (only on the way down)
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- insets, thumbnailHeader, taskId, mService.mCurConfiguration.orientation);
+ insets, thumbnailHeader, taskId, mService.mCurConfiguration.uiMode,
+ mService.mCurConfiguration.orientation);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a5237ca..cd485c5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1312,6 +1312,12 @@
}
}
+static void nativeToggleCapsLock(JNIEnv* env, jclass /* clazz */,
+ jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->getInputManager()->getReader()->toggleCapsLockState(deviceId);
+}
+
static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobjectArray windowHandleObjArray) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1508,6 +1514,8 @@
(void*) nativeSetInputFilterEnabled },
{ "nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIIII)I",
(void*) nativeInjectInputEvent },
+ { "nativeToggleCapsLock", "(JI)V",
+ (void*) nativeToggleCapsLock },
{ "nativeSetInputWindows", "(J[Lcom/android/server/input/InputWindowHandle;)V",
(void*) nativeSetInputWindows },
{ "nativeSetFocusedApplication", "(JLcom/android/server/input/InputApplicationHandle;)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 42b5705..fdea84b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3890,9 +3890,6 @@
// back in to the service.
final long ident = mInjector.binderClearCallingIdentity();
try {
- if (isManagedProfile(userHandle)) {
- mLockPatternUtils.setSeparateProfileChallengeEnabled(userHandle, true);
- }
if (!TextUtils.isEmpty(password)) {
mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
} else {
@@ -3981,6 +3978,15 @@
&& timeMs > admin.maximumTimeToUnlock) {
timeMs = admin.maximumTimeToUnlock;
}
+ // If userInfo.id is a managed profile, we also need to look at
+ // the policies set on the parent.
+ if (admin.hasParentActiveAdmin()) {
+ final ActiveAdmin parentAdmin = admin.getParentActiveAdmin();
+ if (parentAdmin.maximumTimeToUnlock > 0
+ && timeMs > parentAdmin.maximumTimeToUnlock) {
+ timeMs = parentAdmin.maximumTimeToUnlock;
+ }
+ }
}
}
@@ -4014,30 +4020,57 @@
}
enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
- long time = 0;
-
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.maximumTimeToUnlock : time;
+ return admin != null ? admin.maximumTimeToUnlock : 0;
}
-
// Return the strictest policy across all participating admins.
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(
userHandle, parent);
- final int N = admins.size();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = admins.get(i);
- if (time == 0) {
- time = admin.maximumTimeToUnlock;
- } else if (admin.maximumTimeToUnlock != 0
- && time > admin.maximumTimeToUnlock) {
- time = admin.maximumTimeToUnlock;
+ return getMaximumTimeToLockPolicyFromAdmins(admins);
+ }
+ }
+
+ @Override
+ public long getMaximumTimeToLockForUserAndProfiles(int userHandle) {
+ if (!mHasFeature) {
+ return 0;
+ }
+ enforceFullCrossUsersPermission(userHandle);
+ synchronized (this) {
+ // All admins for this user.
+ ArrayList<ActiveAdmin> admins = new ArrayList<ActiveAdmin>();
+ for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
+ DevicePolicyData policy = getUserData(userInfo.id);
+ admins.addAll(policy.mAdminList);
+ // If it is a managed profile, it may have parent active admins
+ if (userInfo.isManagedProfile()) {
+ for (ActiveAdmin admin : policy.mAdminList) {
+ if (admin.hasParentActiveAdmin()) {
+ admins.add(admin.getParentActiveAdmin());
+ }
+ }
}
}
- return time;
+ return getMaximumTimeToLockPolicyFromAdmins(admins);
}
}
+ private long getMaximumTimeToLockPolicyFromAdmins(List<ActiveAdmin> admins) {
+ long time = 0;
+ final int N = admins.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin admin = admins.get(i);
+ if (time == 0) {
+ time = admin.maximumTimeToUnlock;
+ } else if (admin.maximumTimeToUnlock != 0
+ && time > admin.maximumTimeToUnlock) {
+ time = admin.maximumTimeToUnlock;
+ }
+ }
+ return time;
+ }
+
@Override
public void lockNow(boolean parent) {
if (!mHasFeature) {
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 8e11511..2f20a4b 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -59,6 +59,7 @@
import android.os.MessageQueue;
import android.os.Messenger;
import android.os.MessageQueue.IdleHandler;
+import android.os.Process;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -690,6 +691,7 @@
assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType());
// Test getActiveNetwork()
assertNotNull(mCm.getActiveNetwork());
+ assertEquals(mCm.getActiveNetwork(), mCm.getActiveNetworkForUid(Process.myUid()));
switch (transport) {
case TRANSPORT_WIFI:
assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork());
@@ -713,6 +715,7 @@
assertNull(mCm.getActiveNetworkInfo());
// Test getActiveNetwork()
assertNull(mCm.getActiveNetwork());
+ assertNull(mCm.getActiveNetworkForUid(Process.myUid()));
// Test getAllNetworks()
assertEquals(0, mCm.getAllNetworks().length);
}
diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
index 5b70c17..07280bc 100644
--- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
@@ -62,7 +62,7 @@
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
for (int i = nums.length - 1; i >=0; --i) {
- root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
assertEquals(nums[i], calls.size());
calls.clear();
}
@@ -92,7 +92,7 @@
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
for (int i = uris.length - 1; i >=0; --i) {
- root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
assertEquals(nums[i], calls.size());
calls.clear();
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index beec40f..0aeb96f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -144,6 +144,7 @@
private long mLastAppIdleParoledTime;
private volatile boolean mPendingOneTimeCheckIdleStates;
+ private boolean mSystemServicesReady = false;
@GuardedBy("mLock")
private AppIdleHistory mAppIdleHistory;
@@ -232,6 +233,8 @@
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
+
+ mSystemServicesReady = true;
} else if (phase == PHASE_BOOT_COMPLETED) {
setAppIdleParoled(getContext().getSystemService(BatteryManager.class).isCharging());
}
@@ -810,28 +813,30 @@
// retain this for safety).
return false;
}
- try {
- // We allow all whitelisted apps, including those that don't want to be whitelisted
- // for idle mode, because app idle (aka app standby) is really not as big an issue
- // for controlling who participates vs. doze mode.
- if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+ if (mSystemServicesReady) {
+ try {
+ // We allow all whitelisted apps, including those that don't want to be whitelisted
+ // for idle mode, because app idle (aka app standby) is really not as big an issue
+ // for controlling who participates vs. doze mode.
+ if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+ return false;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+
+ if (isActiveDeviceAdmin(packageName, userId)) {
return false;
}
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- if (isActiveDeviceAdmin(packageName, userId)) {
- return false;
- }
+ if (isActiveNetworkScorer(packageName)) {
+ return false;
+ }
- if (isActiveNetworkScorer(packageName)) {
- return false;
- }
-
- if (mAppWidgetManager != null
- && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
- return false;
+ if (mAppWidgetManager != null
+ && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
+ return false;
+ }
}
if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 0bb88a7..db40416 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -492,6 +492,21 @@
SortedVector<String8> reasons;
};
+struct Feature {
+ Feature() : required(false), version(-1) {}
+ Feature(bool required, int32_t version = -1) : required(required), version(version) {}
+
+ /**
+ * Whether the feature is required.
+ */
+ bool required;
+
+ /**
+ * What version of the feature is requested.
+ */
+ int32_t version;
+};
+
/**
* Represents a <feature-group> tag in the AndroidManifest.xml
*/
@@ -506,7 +521,7 @@
/**
* Explicit features defined in the group
*/
- KeyedVector<String8, bool> features;
+ KeyedVector<String8, Feature> features;
/**
* OpenGL ES version required
@@ -541,11 +556,18 @@
const size_t numFeatures = grp.features.size();
for (size_t i = 0; i < numFeatures; i++) {
- const bool required = grp.features[i];
+ const Feature& feature = grp.features[i];
+ const bool required = feature.required;
+ const int32_t version = feature.version;
const String8& featureName = grp.features.keyAt(i);
- printf(" uses-feature%s: name='%s'\n", (required ? "" : "-not-required"),
+ printf(" uses-feature%s: name='%s'", (required ? "" : "-not-required"),
ResTable::normalizeForOutput(featureName.string()).string());
+
+ if (version > 0) {
+ printf(" version='%d'", version);
+ }
+ printf("\n");
}
const size_t numImpliedFeatures =
@@ -590,15 +612,15 @@
static void addParentFeatures(FeatureGroup* grp, const String8& name) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
- grp->features.add(String8("android.hardware.camera"), true);
+ grp->features.add(String8("android.hardware.camera"), Feature(true));
} else if (name == "android.hardware.location.gps" ||
name == "android.hardware.location.network") {
- grp->features.add(String8("android.hardware.location"), true);
+ grp->features.add(String8("android.hardware.location"), Feature(true));
} else if (name == "android.hardware.touchscreen.multitouch") {
- grp->features.add(String8("android.hardware.touchscreen"), true);
+ grp->features.add(String8("android.hardware.touchscreen"), Feature(true));
} else if (name == "android.hardware.touchscreen.multitouch.distinct") {
- grp->features.add(String8("android.hardware.touchscreen.multitouch"), true);
- grp->features.add(String8("android.hardware.touchscreen"), true);
+ grp->features.add(String8("android.hardware.touchscreen.multitouch"), Feature(true));
+ grp->features.add(String8("android.hardware.touchscreen"), Feature(true));
} else if (name == "android.hardware.opengles.aep") {
const int openGLESVersion31 = 0x00030001;
if (openGLESVersion31 > grp->openGLESVersion) {
@@ -727,6 +749,9 @@
return 1;
}
+ // Source for AndroidManifest.xml
+ const String8 manifestFile = String8::format("%s@AndroidManifest.xml", filename);
+
// The dynamicRefTable can be null if there are no resources for this asset cookie.
// This fine.
const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
@@ -1424,10 +1449,28 @@
} else if (tag == "uses-feature") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- int req = AaptXml::getIntegerAttribute(tree,
- REQUIRED_ATTR, 1);
+ const char* androidSchema =
+ "http://schemas.android.com/apk/res/android";
- commonFeatures.features.add(name, req);
+ int32_t req = AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1,
+ &error);
+ if (error != "") {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:required': %s",
+ error.string());
+ goto bail;
+ }
+
+ int32_t version = AaptXml::getIntegerAttribute(tree, androidSchema,
+ "version", 0, &error);
+ if (error != "") {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:version': %s",
+ error.string());
+ goto bail;
+ }
+
+ commonFeatures.features.add(name, Feature(req != 0, version));
if (req) {
addParentFeatures(&commonFeatures, name);
}
@@ -1751,12 +1794,27 @@
}
}
} else if (withinFeatureGroup && tag == "uses-feature") {
+ const String8 androidSchema("http://schemas.android.com/apk/res/android");
FeatureGroup& top = featureGroups.editTop();
String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- top.features.add(name, true);
+ Feature feature(true);
+
+ int32_t featureVers = AaptXml::getIntegerAttribute(
+ tree, androidSchema.string(), "version", 0, &error);
+ if (error == "") {
+ feature.version = featureVers;
+ } else {
+ SourcePos(manifestFile, tree.getLineNumber()).error(
+ "failed to read attribute 'android:version': %s",
+ error.string());
+ goto bail;
+ }
+
+ top.features.add(name, feature);
addParentFeatures(&top, name);
+
} else {
int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR,
&error);
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 876a422..3a1e2bb 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -66,6 +66,7 @@
ResourceValues.cpp \
SdkConstants.cpp \
StringPool.cpp \
+ xml/XmlActionExecutor.cpp \
xml/XmlDom.cpp \
xml/XmlPullParser.cpp \
xml/XmlUtil.cpp
@@ -107,6 +108,7 @@
SdkConstants_test.cpp \
StringPool_test.cpp \
ValueVisitor_test.cpp \
+ xml/XmlActionExecutor_test.cpp \
xml/XmlDom_test.cpp \
xml/XmlPullParser_test.cpp \
xml/XmlUtil_test.cpp
@@ -129,33 +131,40 @@
libbase \
libprotobuf-cpp-lite_static
-# Do not add any shared libraries. AAPT2 is built to run on many
-# environments that may not have the required dependencies.
-hostSharedLibs :=
-ifneq ($(strip $(USE_MINGW)),)
- hostStaticLibs += libz
-else
- hostLdLibs += -lz
-endif
+# Statically link libz for MinGW (Win SDK under Linux),
+# and dynamically link for all others.
+hostStaticLibs_windows := libz
+hostLdLibs_linux := -lz
+hostLdLibs_darwin := -lz
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
+cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
+cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
# ==========================================================
+# NOTE: Do not add any shared libraries.
+# AAPT2 is built to run on many environments
+# that may not have the required dependencies.
+# ==========================================================
+
+# ==========================================================
# Build the host static library: libaapt2
# ==========================================================
include $(CLEAR_VARS)
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
LOCAL_MODULE := libaapt2
-
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(sources)
-LOCAL_STATIC_LIBRARIES += $(hostStaticLibs)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-LOCAL_C_INCLUDES += $(protoIncludes)
-
+LOCAL_STATIC_LIBRARIES := $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
include $(BUILD_HOST_STATIC_LIBRARY)
# ==========================================================
@@ -164,16 +173,18 @@
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt2_tests
LOCAL_MODULE_TAGS := tests
-
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(testSources)
-
-LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
-LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-LOCAL_C_INCLUDES += $(protoIncludes)
-
+LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS := $(hostLdLibs)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
include $(BUILD_HOST_NATIVE_TEST)
# ==========================================================
@@ -181,16 +192,18 @@
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := aapt2
-
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CFLAGS := $(cFlags)
+LOCAL_CFLAGS_darwin := $(cFlags_darwin)
+LOCAL_CFLAGS_windows := $(cFlags_windows)
+LOCAL_CPPFLAGS := $(cppFlags)
+LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(main) $(toolSources)
-
-LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
-LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
-LOCAL_LDLIBS += $(hostLdLibs)
-LOCAL_CFLAGS += $(cFlags)
-LOCAL_CPPFLAGS += $(cppFlags)
-LOCAL_C_INCLUDES += $(protoIncludes)
-
+LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
+LOCAL_LDLIBS := $(hostLdLibs)
+LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
+LOCAL_LDLIBS_linux := $(hostLdLibs_linux)
include $(BUILD_HOST_EXECUTABLE)
ifeq ($(ONE_SHOT_MAKEFILE),)
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index ab4d284..e86f2a8 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -21,6 +21,7 @@
#include "util/StringPiece.h"
#include "util/Util.h"
+#include <android-base/macros.h>
#include <iostream>
#include <sstream>
#include <string>
@@ -46,7 +47,11 @@
DiagMessage(const Source& src) : mSource(src) {
}
- template <typename T> DiagMessage& operator<<(const T& value) {
+ DiagMessage(size_t line) : mSource(Source().withLine(line)) {
+ }
+
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
mMessage << value;
return *this;
}
@@ -59,36 +64,82 @@
struct IDiagnostics {
virtual ~IDiagnostics() = default;
- virtual void error(const DiagMessage& message) = 0;
- virtual void warn(const DiagMessage& message) = 0;
- virtual void note(const DiagMessage& message) = 0;
+ enum class Level {
+ Note,
+ Warn,
+ Error
+ };
+
+ virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
+
+ virtual void error(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Error, actual);
+ }
+
+ virtual void warn(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Warn, actual);
+ }
+
+ virtual void note(const DiagMessage& message) {
+ DiagMessageActual actual = message.build();
+ log(Level::Note, actual);
+ }
};
-struct StdErrDiagnostics : public IDiagnostics {
+class StdErrDiagnostics : public IDiagnostics {
+public:
+ StdErrDiagnostics() = default;
+
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ const char* tag;
+
+ switch (level) {
+ case Level::Error:
+ mNumErrors++;
+ if (mNumErrors > 20) {
+ return;
+ }
+ tag = "error";
+ break;
+
+ case Level::Warn:
+ tag = "warn";
+ break;
+
+ case Level::Note:
+ tag = "note";
+ break;
+ }
+
+ if (!actualMsg.source.path.empty()) {
+ std::cerr << actualMsg.source << ": ";
+ }
+ std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ }
+
+private:
size_t mNumErrors = 0;
- void emit(const DiagMessage& msg, const char* tag) {
- DiagMessageActual actual = msg.build();
- if (!actual.source.path.empty()) {
- std::cerr << actual.source << ": ";
- }
- std::cerr << tag << actual.message << "." << std::endl;
+ DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+};
+
+class SourcePathDiagnostics : public IDiagnostics {
+public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
}
- void error(const DiagMessage& msg) override {
- if (mNumErrors < 20) {
- emit(msg, "error: ");
- }
- mNumErrors++;
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ actualMsg.source.path = mSource.path;
+ mDiag->log(level, actualMsg);
}
- void warn(const DiagMessage& msg) override {
- emit(msg, "warn: ");
- }
+private:
+ Source mSource;
+ IDiagnostics* mDiag;
- void note(const DiagMessage& msg) override {
- emit(msg, "note: ");
- }
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 180bd11..d6c52ab 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -28,33 +28,23 @@
namespace aapt {
-struct ResourceTableTest : public ::testing::Test {
- struct EmptyDiagnostics : public IDiagnostics {
- void error(const DiagMessage& msg) override {}
- void warn(const DiagMessage& msg) override {}
- void note(const DiagMessage& msg) override {}
- };
-
- EmptyDiagnostics mDiagnostics;
-};
-
-TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
+TEST(ResourceTableTest, FailToAddResourceWithBadName) {
ResourceTable table;
EXPECT_FALSE(table.addResource(
ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
ConfigDescription{}, "",
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_FALSE(table.addResource(
ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
ConfigDescription{}, "",
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
}
-TEST_F(ResourceTableTest, AddOneResource) {
+TEST(ResourceTableTest, AddOneResource) {
ResourceTable table;
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
@@ -62,12 +52,12 @@
"",
test::ValueBuilder<Id>()
.setSource("test/path/file.xml", 23u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
}
-TEST_F(ResourceTableTest, AddMultipleResources) {
+TEST(ResourceTableTest, AddMultipleResources) {
ResourceTable table;
ConfigDescription config;
@@ -79,21 +69,21 @@
config,
"",
test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/id"),
config,
"",
test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
config,
"",
test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
@@ -102,7 +92,7 @@
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
.setSource("test/path/file.xml", 20u)
.build(),
- &mDiagnostics));
+ test::getDiagnostics()));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
@@ -111,37 +101,37 @@
languageConfig));
}
-TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
+TEST(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
- "", util::make_unique<Attribute>(true), &mDiagnostics));
+ "", util::make_unique<Attribute>(true), test::getDiagnostics()));
Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
ASSERT_NE(nullptr, attr);
EXPECT_TRUE(attr->isWeak());
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
- "", util::make_unique<Attribute>(false), &mDiagnostics));
+ "", util::make_unique<Attribute>(false), test::getDiagnostics()));
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
ASSERT_NE(nullptr, attr);
EXPECT_FALSE(attr->isWeak());
}
-TEST_F(ResourceTableTest, ProductVaryingValues) {
+TEST(ResourceTableTest, ProductVaryingValues) {
ResourceTable table;
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
test::parseConfigOrDie("land"),
"tablet",
util::make_unique<Id>(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
test::parseConfigOrDie("land"),
"phone",
util::make_unique<Id>(),
- &mDiagnostics));
+ test::getDiagnostics()));
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
test::parseConfigOrDie("land"),
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index e93c2fba..2b2d348 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -20,8 +20,6 @@
#include <gtest/gtest.h>
#include <string>
-using namespace android;
-
namespace aapt {
TEST(StringPoolTest, InsertOneString) {
@@ -171,24 +169,28 @@
}
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ using namespace android; // For NO_ERROR on Windows.
+
StringPool pool;
BigBuffer buffer(1024);
StringPool::flattenUtf8(&buffer, pool);
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- android::ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
}
TEST(StringPoolTest, FlattenOddCharactersUtf16) {
+ using namespace android; // For NO_ERROR on Windows.
+
StringPool pool;
pool.makeRef(u"\u093f");
BigBuffer buffer(1024);
StringPool::flattenUtf16(&buffer, pool);
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- android::ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
size_t len = 0;
const char16_t* str = test.stringAt(0, &len);
EXPECT_EQ(1u, len);
@@ -199,6 +201,8 @@
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, FlattenUtf8) {
+ using namespace android; // For NO_ERROR on Windows.
+
StringPool pool;
StringPool::Ref ref1 = pool.makeRef(u"hello");
@@ -219,8 +223,8 @@
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
{
- android::ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
EXPECT_EQ(util::getString(test, 0), u"hello");
EXPECT_EQ(util::getString(test, 1), u"goodbye");
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index be26b52..99c2077 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -20,6 +20,8 @@
#include "compile/PseudolocaleGenerator.h"
#include "compile/Pseudolocalizer.h"
+#include <algorithm>
+
namespace aapt {
std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index da81046..28a7928 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -24,6 +24,7 @@
#include "util/BigBuffer.h"
#include <android-base/macros.h>
+#include <algorithm>
#include <type_traits>
#include <numeric>
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 3eac633..570cd96 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -21,6 +21,7 @@
#include "xml/XmlDom.h"
#include <androidfw/ResourceTypes.h>
+#include <algorithm>
#include <utils/misc.h>
#include <vector>
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index fef5ca3..4e6eb81 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -46,6 +46,8 @@
::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
XmlFlattenerOptions options = {}) {
+ using namespace android; // For NO_ERROR on windows because it is a macro.
+
BigBuffer buffer(1024);
XmlFlattener flattener(&buffer, options);
if (!flattener.consume(mContext.get(), doc)) {
@@ -53,7 +55,7 @@
}
std::unique_ptr<uint8_t[]> data = util::copy(buffer);
- if (outTree->setTo(data.get(), buffer.size(), true) != android::NO_ERROR) {
+ if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
return ::testing::AssertionFailure() << "flattened XML is corrupt";
}
return ::testing::AssertionSuccess();
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index da96b84..d3860a5 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -49,16 +49,18 @@
};
TEST_F(AnnotationProcessorTest, EmitsDeprecated) {
- ASSERT_TRUE(parse(R"EOF(
+ const char* xmlInput = R"EOF(
<resources>
- <declare-styleable name="foo">
+ <declare-styleable name="foo">
<!-- Some comment, and it should contain
a marker word, something that marks
this resource as nor needed.
{@deprecated That's the marker! } -->
<attr name="autoText" format="boolean" />
</declare-styleable>
- </resources>)EOF"));
+ </resources>)EOF";
+
+ ASSERT_TRUE(parse(xmlInput));
Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/autoText");
ASSERT_NE(nullptr, attr);
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 092bab24..32b8600 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -318,7 +318,7 @@
}
std::unique_ptr<IntMember> indexMember = util::make_unique<IntMember>(
- sortedAttributes[i].fieldName, i);
+ sortedAttributes[i].fieldName, static_cast<uint32_t>(i));
AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder();
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 4f041b8..370e78a 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -239,13 +239,15 @@
ASSERT_TRUE(generator.generate(u"android", &out));
std::string actual = out.str();
- EXPECT_NE(std::string::npos, actual.find(
- R"EOF(/**
+ const char* expectedText =
+R"EOF(/**
* This is a comment
* @deprecated
*/
@Deprecated
- public static final int foo=0x01010000;)EOF"));
+ public static final int foo=0x01010000;)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedText));
}
TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index a9ec318..e7210db 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -104,28 +104,34 @@
std::string actual;
ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual));
- EXPECT_NE(std::string::npos, actual.find(
+ const char* expectedAccessInternet =
R"EOF( /**
* Required to access the internet.
* Added in API 1.
*/
- public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"));
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF";
- EXPECT_NE(std::string::npos, actual.find(
+ EXPECT_NE(std::string::npos, actual.find(expectedAccessInternet));
+
+ const char* expectedPlayOutside =
R"EOF( /**
* @deprecated This permission is for playing outside.
*/
@Deprecated
- public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"));
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF";
- EXPECT_NE(std::string::npos, actual.find(
+ EXPECT_NE(std::string::npos, actual.find(expectedPlayOutside));
+
+ const char* expectedSecret =
R"EOF( /**
* This is a private permission for system only!
* @hide
* @SystemApi
*/
@android.annotation.SystemApi
- public static final String SECRET="android.permission.SECRET";)EOF"));
+ public static final String SECRET="android.permission.SECRET";)EOF";
+
+ EXPECT_NE(std::string::npos, actual.find(expectedSecret));
}
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 9baf1d8..3779638 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -17,66 +17,199 @@
#include "ResourceUtils.h"
#include "link/ManifestFixer.h"
#include "util/Util.h"
+#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
namespace aapt {
-static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) {
- xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
- if (!attr) {
- context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
- << "missing 'package' attribute");
- } else if (ResourceUtils::isReference(attr->value)) {
- context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
- << "value for attribute 'package' must not be a "
- "reference");
- } else if (!util::isJavaPackageName(attr->value)) {
- context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
- << "invalid package name '" << attr->value << "'");
- } else {
- return true;
+/**
+ * This is how PackageManager builds class names from AndroidManifest.xml entries.
+ */
+static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
+ SourcePathDiagnostics* diag) {
+ std::u16string className = attr->value;
+ if (className.find(u'.') == std::u16string::npos) {
+ // There is no '.', so add one to the beginning.
+ className = u".";
+ className += attr->value;
}
+
+ // We allow unqualified class names (ie: .HelloActivity)
+ // Since we don't know the package name, we can just make a fake one here and
+ // the test will be identical as long as the real package name is valid too.
+ Maybe<std::u16string> fullyQualifiedClassName =
+ util::getFullyQualifiedClassName(u"a", className);
+
+ StringPiece16 qualifiedClassName = fullyQualifiedClassName
+ ? fullyQualifiedClassName.value() : className;
+ if (!util::isJavaClassName(qualifiedClassName)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'android:name' in <"
+ << el->name << "> tag must be a valid Java class name");
+ return false;
+ }
+ return true;
+}
+
+static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ return true;
+}
+
+static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) {
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) {
+ return nameIsJavaClassName(el, attr, diag);
+ }
+ diag->error(DiagMessage(el->lineNumber)
+ << "<" << el->name << "> is missing attribute 'android:name'");
return false;
}
-static bool includeVersionName(IAaptContext* context, const Source& source,
- const StringPiece16& versionName, xml::Element* manifestEl) {
- if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) {
- return true;
+static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+ xml::Attribute* attr = el->findAttribute({}, u"package");
+ if (!attr) {
+ diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute");
+ return false;
+ } else if (ResourceUtils::isReference(attr->value)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag must not be a reference");
+ return false;
+ } else if (!util::isJavaPackageName(attr->value)) {
+ diag->error(DiagMessage(el->lineNumber)
+ << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
+ << attr->value << "'");
+ return false;
}
-
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"versionName", versionName.toString() });
return true;
}
-static bool includeVersionCode(IAaptContext* context, const Source& source,
- const StringPiece16& versionCode, xml::Element* manifestEl) {
- if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) {
+bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) {
+ // First verify some options.
+ if (mOptions.renameManifestPackage) {
+ if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) {
+ diag->error(DiagMessage() << "invalid manifest package override '"
+ << mOptions.renameManifestPackage.value() << "'");
+ return false;
+ }
+ }
+
+ if (mOptions.renameInstrumentationTargetPackage) {
+ if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) {
+ diag->error(DiagMessage() << "invalid instrumentation target package override '"
+ << mOptions.renameInstrumentationTargetPackage.value() << "'");
+ return false;
+ }
+ }
+
+ // Common intent-filter actions.
+ xml::XmlNodeAction intentFilterAction;
+ intentFilterAction[u"action"];
+ intentFilterAction[u"category"];
+ intentFilterAction[u"data"];
+
+ // Common meta-data actions.
+ xml::XmlNodeAction metaDataAction;
+
+ // Manifest actions.
+ xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"];
+ manifestAction.action(verifyManifest);
+ manifestAction.action([&](xml::Element* el) -> bool {
+ if (mOptions.versionNameDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) {
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid,
+ u"versionName",
+ mOptions.versionNameDefault.value() });
+ }
+ }
+
+ if (mOptions.versionCodeDefault) {
+ if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) {
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid,
+ u"versionCode",
+ mOptions.versionCodeDefault.value() });
+ }
+ }
return true;
- }
+ });
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"versionCode", versionCode.toString() });
- return true;
-}
+ // Uses-sdk actions.
+ manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool {
+ if (mOptions.minSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"minSdkVersion",
+ mOptions.minSdkVersionDefault.value() });
+ }
-static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el,
- const ManifestFixerOptions& options) {
- if (options.minSdkVersionDefault &&
- el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
- // There was no minSdkVersion defined and we have a default to assign.
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() });
- }
+ if (mOptions.targetSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"targetSdkVersion",
+ mOptions.targetSdkVersionDefault.value() });
+ }
+ return true;
+ });
- if (options.targetSdkVersionDefault &&
- el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
- // There was no targetSdkVersion defined and we have a default to assign.
- el->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"targetSdkVersion",
- options.targetSdkVersionDefault.value() });
- }
+ // Instrumentation actions.
+ manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool {
+ if (!mOptions.renameInstrumentationTargetPackage) {
+ return true;
+ }
+
+ if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) {
+ attr->value = mOptions.renameInstrumentationTargetPackage.value();
+ }
+ return true;
+ });
+
+ manifestAction[u"eat-comment"];
+ manifestAction[u"protected-broadcast"];
+ manifestAction[u"uses-permission"];
+ manifestAction[u"permission"];
+ manifestAction[u"permission-tree"];
+ manifestAction[u"permission-group"];
+
+ manifestAction[u"uses-configuration"];
+ manifestAction[u"uses-feature"];
+ manifestAction[u"uses-library"];
+ manifestAction[u"supports-screens"];
+ manifestAction[u"compatible-screens"];
+ manifestAction[u"supports-gl-texture"];
+
+ // Application actions.
+ xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"];
+ applicationAction.action(optionalNameIsJavaClassName);
+
+ // Activity actions.
+ applicationAction[u"activity"].action(requiredNameIsJavaClassName);
+ applicationAction[u"activity"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"activity"][u"meta-data"] = metaDataAction;
+
+ // Activity alias actions.
+ applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction;
+
+ // Service actions.
+ applicationAction[u"service"].action(requiredNameIsJavaClassName);
+ applicationAction[u"service"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"service"][u"meta-data"] = metaDataAction;
+
+ // Receiver actions.
+ applicationAction[u"receiver"].action(requiredNameIsJavaClassName);
+ applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction;
+ applicationAction[u"receiver"][u"meta-data"] = metaDataAction;
+
+ // Provider actions.
+ applicationAction[u"provider"].action(requiredNameIsJavaClassName);
+ applicationAction[u"provider"][u"grant-uri-permissions"];
+ applicationAction[u"provider"][u"meta-data"] = metaDataAction;
+ applicationAction[u"provider"][u"path-permissions"];
return true;
}
@@ -103,14 +236,7 @@
StringPiece16 mPackage;
};
-static bool renameManifestPackage(IAaptContext* context, const Source& source,
- const StringPiece16& packageOverride, xml::Element* manifestEl) {
- if (!util::isJavaPackageName(packageOverride)) {
- context->getDiagnostics()->error(DiagMessage() << "invalid manifest package override '"
- << packageOverride << "'");
- return false;
- }
-
+static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) {
xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
// We've already verified that the manifest element is present, with a package name specified.
@@ -124,32 +250,6 @@
return true;
}
-static bool renameInstrumentationTargetPackage(IAaptContext* context, const Source& source,
- const StringPiece16& packageOverride,
- xml::Element* manifestEl) {
- if (!util::isJavaPackageName(packageOverride)) {
- context->getDiagnostics()->error(DiagMessage()
- << "invalid instrumentation target package override '"
- << packageOverride << "'");
- return false;
- }
-
- xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation");
- if (!instrumentationEl) {
- // No error if there is no work to be done.
- return true;
- }
-
- xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage");
- if (!attr) {
- // No error if there is no work to be done.
- return true;
- }
-
- attr->value = packageOverride.toString();
- return true;
-}
-
bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
xml::Element* root = xml::findRootElement(doc->root.get());
if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
@@ -158,59 +258,31 @@
return false;
}
- if (!verifyManifest(context, doc->file.source, root)) {
- return false;
- }
-
- if (mOptions.versionCodeDefault) {
- if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(),
- root)) {
- return false;
- }
- }
-
- if (mOptions.versionNameDefault) {
- if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(),
- root)) {
- return false;
- }
- }
-
- if (mOptions.renameManifestPackage) {
- // Rename manifest package.
- if (!renameManifestPackage(context, doc->file.source,
- mOptions.renameManifestPackage.value(), root)) {
- return false;
- }
- }
-
- if (mOptions.renameInstrumentationTargetPackage) {
- if (!renameInstrumentationTargetPackage(context, doc->file.source,
- mOptions.renameInstrumentationTargetPackage.value(),
- root)) {
- return false;
- }
- }
-
- bool foundUsesSdk = false;
- for (xml::Element* el : root->getChildElements()) {
- if (!el->namespaceUri.empty()) {
- continue;
- }
-
- if (el->name == u"uses-sdk") {
- foundUsesSdk = true;
- fixUsesSdk(context, doc->file.source, el, mOptions);
- }
- }
-
- if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) {
+ if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)
+ && root->findChild({}, u"uses-sdk") == nullptr) {
+ // Auto insert a <uses-sdk> element.
std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
usesSdk->name = u"uses-sdk";
- fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions);
root->addChild(std::move(usesSdk));
}
+ xml::XmlActionExecutor executor;
+ if (!buildRules(&executor, context->getDiagnostics())) {
+ return false;
+ }
+
+ if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
+ doc)) {
+ return false;
+ }
+
+ if (mOptions.renameManifestPackage) {
+ // Rename manifest package outside of the XmlActionExecutor.
+ // We need to extract the old package name and FullyQualify all class names.
+ if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) {
+ return false;
+ }
+ }
return true;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index b8d9c83..4d9356a 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -19,6 +19,7 @@
#include "process/IResourceTableConsumer.h"
#include "util/Maybe.h"
+#include "xml/XmlActionExecutor.h"
#include "xml/XmlDom.h"
#include <string>
@@ -38,13 +39,17 @@
* Verifies that the manifest is correctly formed and inserts defaults
* where specified with ManifestFixerOptions.
*/
-struct ManifestFixer : public IXmlResourceConsumer {
- ManifestFixerOptions mOptions;
-
+class ManifestFixer : public IXmlResourceConsumer {
+public:
ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
}
bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+private:
+ bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag);
+
+ ManifestFixerOptions mOptions;
};
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 18c47df..f993720 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -166,9 +166,9 @@
std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
- <application name=".MainApplication" text="hello">
- <activity name=".activity.Start" />
- <receiver name="com.google.android.Receiver" />
+ <application android:name=".MainApplication" text="hello">
+ <activity android:name=".activity.Start" />
+ <receiver android:name="com.google.android.Receiver" />
</application>
</manifest>)EOF", options);
ASSERT_NE(nullptr, doc);
@@ -185,7 +185,7 @@
xml::Element* applicationEl = manifestEl->findChild({}, u"application");
ASSERT_NE(nullptr, applicationEl);
- attr = applicationEl->findAttribute({}, u"name");
+ attr = applicationEl->findAttribute(xml::kSchemaAndroid, u"name");
ASSERT_NE(nullptr, attr);
EXPECT_EQ(std::u16string(u"android.MainApplication"), attr->value);
@@ -197,14 +197,14 @@
el = applicationEl->findChild({}, u"activity");
ASSERT_NE(nullptr, el);
- attr = el->findAttribute({}, u"name");
+ attr = el->findAttribute(xml::kSchemaAndroid, u"name");
ASSERT_NE(nullptr, el);
EXPECT_EQ(std::u16string(u"android.activity.Start"), attr->value);
el = applicationEl->findChild({}, u"receiver");
ASSERT_NE(nullptr, el);
- attr = el->findAttribute({}, u"name");
+ attr = el->findAttribute(xml::kSchemaAndroid, u"name");
ASSERT_NE(nullptr, el);
EXPECT_EQ(std::u16string(u"com.google.android.Receiver"), attr->value);
}
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 86883f8..82e4fb0 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -388,6 +388,10 @@
std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
const Source& source,
IDiagnostics* diag) {
+ // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
+ // causes errors when qualifying it with android::
+ using namespace android;
+
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
if (!pbTable.has_string_pool()) {
@@ -395,29 +399,29 @@
return {};
}
- android::ResStringPool valuePool;
- android::status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
- pbTable.string_pool().data().size());
- if (result != android::NO_ERROR) {
+ ResStringPool valuePool;
+ status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
+ pbTable.string_pool().data().size());
+ if (result != NO_ERROR) {
diag->error(DiagMessage(source) << "invalid string pool");
return {};
}
- android::ResStringPool sourcePool;
+ ResStringPool sourcePool;
if (pbTable.has_source_pool()) {
result = sourcePool.setTo(pbTable.source_pool().data().data(),
pbTable.source_pool().data().size());
- if (result != android::NO_ERROR) {
+ if (result != NO_ERROR) {
diag->error(DiagMessage(source) << "invalid source pool");
return {};
}
}
- android::ResStringPool symbolPool;
+ ResStringPool symbolPool;
if (pbTable.has_symbol_pool()) {
result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
pbTable.symbol_pool().data().size());
- if (result != android::NO_ERROR) {
+ if (result != NO_ERROR) {
diag->error(DiagMessage(source) << "invalid symbol pool");
return {};
}
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 0f7649b..4bfdb12 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -18,6 +18,7 @@
#include "ResourceTable.h"
#include "split/TableSplitter.h"
+#include <algorithm>
#include <map>
#include <set>
#include <unordered_map>
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 8c56ebc..8eb4bc8 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -238,7 +238,7 @@
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
StdErrDiagnostics diag;
- std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {});
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml"));
assert(doc);
return doc;
}
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 348c32a..faccd477 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -41,15 +41,20 @@
namespace test {
struct DummyDiagnosticsImpl : public IDiagnostics {
- void error(const DiagMessage& message) override {
- DiagMessageActual actual = message.build();
- std::cerr << actual.source << ": error: " << actual.message << "." << std::endl;
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ switch (level) {
+ case Level::Note:
+ return;
+
+ case Level::Warn:
+ std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl;
+ break;
+
+ case Level::Error:
+ std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl;
+ break;
+ }
}
- void warn(const DiagMessage& message) override {
- DiagMessageActual actual = message.build();
- std::cerr << actual.source << ": warn: " << actual.message << "." << std::endl;
- }
- void note(const DiagMessage& message) override {}
};
inline IDiagnostics* getDiagnostics() {
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 33b505e..ec46751 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -26,7 +26,7 @@
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
#include <android-base/macros.h>
-
+#include <algorithm>
#include <map>
#include <string>
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 6428e98..bb093ab 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -17,6 +17,7 @@
#include "util/Files.h"
#include "util/Util.h"
+#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <dirent.h>
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 10a2803..595db96 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -17,6 +17,8 @@
#ifndef AAPT_MAYBE_H
#define AAPT_MAYBE_H
+#include "util/TypeTraits.h"
+
#include <cassert>
#include <type_traits>
#include <utility>
@@ -276,13 +278,15 @@
}
/**
- * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined.
- * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside
- * Maybe.h.
+ * Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined.
+ * That way the compiler will show an error at the callsite when comparing two Maybe<> objects
+ * whose inner types can't be compared.
*/
template <typename T, typename U>
-auto operator==(const Maybe<T>& a, const Maybe<U>& b)
--> decltype(std::declval<T> == std::declval<U>) {
+typename std::enable_if<
+ has_eq_op<T, U>::value,
+ bool
+>::type operator==(const Maybe<T>& a, const Maybe<U>& b) {
if (a && b) {
return a.value() == b.value();
} else if (!a && !b) {
@@ -295,8 +299,10 @@
* Same as operator== but negated.
*/
template <typename T, typename U>
-auto operator!=(const Maybe<T>& a, const Maybe<U>& b)
--> decltype(std::declval<T> == std::declval<U>) {
+typename std::enable_if<
+ has_eq_op<T, U>::value,
+ bool
+>::type operator!=(const Maybe<T>& a, const Maybe<U>& b) {
return !(a == b);
}
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
new file mode 100644
index 0000000..0ef67ea
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "xml/XmlActionExecutor.h"
+
+namespace aapt {
+namespace xml {
+
+static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
+ return f(el);
+}
+
+static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+ SourcePathDiagnostics* diag) {
+ return f(el, diag);
+}
+
+void XmlNodeAction::action(XmlNodeAction::ActionFunc f) {
+ mActions.emplace_back(std::bind(wrapperOne, std::move(f),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+void XmlNodeAction::action(XmlNodeAction::ActionFuncWithDiag f) {
+ mActions.emplace_back(std::bind(wrapperTwo, std::move(f),
+ std::placeholders::_1,
+ std::placeholders::_2));
+}
+
+static void printElementToDiagMessage(const Element* el, DiagMessage* msg) {
+ *msg << "<";
+ if (!el->namespaceUri.empty()) {
+ *msg << el->namespaceUri << ":";
+ }
+ *msg << el->name << ">";
+}
+
+bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag,
+ Element* el) const {
+ bool error = false;
+ for (const ActionFuncWithDiag& action : mActions) {
+ error |= !action(el, diag);
+ }
+
+ for (Element* childEl : el->getChildElements()) {
+ if (childEl->namespaceUri.empty()) {
+ std::map<std::u16string, XmlNodeAction>::const_iterator iter =
+ mMap.find(childEl->name);
+ if (iter != mMap.end()) {
+ error |= !iter->second.execute(policy, diag, childEl);
+ continue;
+ }
+ }
+
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ DiagMessage errorMsg(childEl->lineNumber);
+ errorMsg << "unknown element ";
+ printElementToDiagMessage(childEl, &errorMsg);
+ errorMsg << " found";
+ diag->error(errorMsg);
+ error = true;
+ }
+ }
+ return !error;
+}
+
+bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag,
+ XmlResource* doc) const {
+ SourcePathDiagnostics sourceDiag(doc->file.source, diag);
+
+ Element* el = findRootElement(doc);
+ if (!el) {
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ sourceDiag.error(DiagMessage() << "no root XML tag found");
+ return false;
+ }
+ return true;
+ }
+
+ if (el->namespaceUri.empty()) {
+ std::map<std::u16string, XmlNodeAction>::const_iterator iter = mMap.find(el->name);
+ if (iter != mMap.end()) {
+ return iter->second.execute(policy, &sourceDiag, el);
+ }
+ }
+
+ if (policy == XmlActionExecutorPolicy::Whitelist) {
+ DiagMessage errorMsg(el->lineNumber);
+ errorMsg << "unknown element ";
+ printElementToDiagMessage(el, &errorMsg);
+ errorMsg << " found";
+ sourceDiag.error(errorMsg);
+ return false;
+ }
+ return true;
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
new file mode 100644
index 0000000..36b94db
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_XMLPATTERN_H
+#define AAPT_XML_XMLPATTERN_H
+
+#include "Diagnostics.h"
+#include "xml/XmlDom.h"
+
+#include <android-base/macros.h>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace xml {
+
+enum class XmlActionExecutorPolicy {
+ /**
+ * Actions on run if elements are matched, errors occur only when actions return false.
+ */
+ None,
+
+ /**
+ * The actions defined must match and run. If an element is found that does not match
+ * an action, an error occurs.
+ */
+ Whitelist,
+};
+
+/**
+ * Contains the actions to perform at this XML node. This is a recursive data structure that
+ * holds XmlNodeActions for child XML nodes.
+ */
+class XmlNodeAction {
+public:
+ using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
+ using ActionFunc = std::function<bool(Element*)>;
+
+ /**
+ * Find or create a child XmlNodeAction that will be performed for the child element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::u16string& name) {
+ return mMap[name];
+ }
+
+ /**
+ * Add an action to be performed at this XmlNodeAction.
+ */
+ void action(ActionFunc f);
+ void action(ActionFuncWithDiag);
+
+private:
+ friend class XmlActionExecutor;
+
+ bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const;
+
+ std::map<std::u16string, XmlNodeAction> mMap;
+ std::vector<ActionFuncWithDiag> mActions;
+};
+
+/**
+ * Allows the definition of actions to execute at specific XML elements defined by their
+ * hierarchy.
+ */
+class XmlActionExecutor {
+public:
+ XmlActionExecutor() = default;
+
+ /**
+ * Find or create a root XmlNodeAction that will be performed for the root XML element
+ * with the name `name`.
+ */
+ XmlNodeAction& operator[](const std::u16string& name) {
+ return mMap[name];
+ }
+
+ /**
+ * Execute the defined actions for this XmlResource.
+ * Returns true if all actions return true, otherwise returns false.
+ */
+ bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const;
+
+private:
+ std::map<std::u16string, XmlNodeAction> mMap;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor);
+};
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLPATTERN_H */
diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp
new file mode 100644
index 0000000..ebf287a
--- /dev/null
+++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Test.h"
+#include "xml/XmlActionExecutor.h"
+
+namespace aapt {
+namespace xml {
+
+TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) {
+ XmlActionExecutor executor;
+ XmlNodeAction& manifestAction = executor[u"manifest"];
+ XmlNodeAction& applicationAction = manifestAction[u"application"];
+
+ Element* manifestEl = nullptr;
+ manifestAction.action([&](Element* manifest) -> bool {
+ manifestEl = manifest;
+ return true;
+ });
+
+ Element* applicationEl = nullptr;
+ applicationAction.action([&](Element* application) -> bool {
+ applicationEl = application;
+ return true;
+ });
+
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom("<manifest><application /></manifest>");
+
+ StdErrDiagnostics diag;
+ ASSERT_TRUE(executor.execute(XmlActionExecutorPolicy::None, &diag, doc.get()));
+ ASSERT_NE(nullptr, manifestEl);
+ EXPECT_EQ(std::u16string(u"manifest"), manifestEl->name);
+
+ ASSERT_NE(nullptr, applicationEl);
+ EXPECT_EQ(std::u16string(u"application"), applicationEl->name);
+}
+
+TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) {
+ XmlActionExecutor executor;
+ executor[u"manifest"][u"application"];
+
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom(
+ "<manifest><application /><activity /></manifest>");
+ StdErrDiagnostics diag;
+ ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get()));
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index d27b62fd..0ce333a 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -228,20 +228,24 @@
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
const Source& source) {
+ // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
+ // causes errors when qualifying it with android::
+ using namespace android;
+
std::unique_ptr<Node> root;
std::stack<Node*> nodeStack;
- android::ResXMLTree tree;
- if (tree.setTo(data, dataLen) != android::NO_ERROR) {
+ ResXMLTree tree;
+ if (tree.setTo(data, dataLen) != NO_ERROR) {
return {};
}
- android::ResXMLParser::event_code_t code;
- while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT &&
- code != android::ResXMLParser::END_DOCUMENT) {
+ ResXMLParser::event_code_t code;
+ while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT &&
+ code != ResXMLParser::END_DOCUMENT) {
std::unique_ptr<Node> newNode;
switch (code) {
- case android::ResXMLParser::START_NAMESPACE: {
+ case ResXMLParser::START_NAMESPACE: {
std::unique_ptr<Namespace> node = util::make_unique<Namespace>();
size_t len;
const char16_t* str16 = tree.getNamespacePrefix(&len);
@@ -257,7 +261,7 @@
break;
}
- case android::ResXMLParser::START_TAG: {
+ case ResXMLParser::START_TAG: {
std::unique_ptr<Element> node = util::make_unique<Element>();
size_t len;
const char16_t* str16 = tree.getElementNamespace(&len);
@@ -276,7 +280,7 @@
break;
}
- case android::ResXMLParser::TEXT: {
+ case ResXMLParser::TEXT: {
std::unique_ptr<Text> node = util::make_unique<Text>();
size_t len;
const char16_t* str16 = tree.getText(&len);
@@ -287,8 +291,8 @@
break;
}
- case android::ResXMLParser::END_NAMESPACE:
- case android::ResXMLParser::END_TAG:
+ case ResXMLParser::END_NAMESPACE:
+ case ResXMLParser::END_TAG:
assert(!nodeStack.empty());
nodeStack.pop();
break;
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
index 7796b3e..319e770 100644
--- a/tools/aapt2/xml/XmlUtil_test.cpp
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -33,22 +33,22 @@
xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
AAPT_ASSERT_TRUE(p);
EXPECT_EQ(std::u16string(u"a"), p.value().package);
- EXPECT_EQ(false, p.value().privateNamespace);
+ EXPECT_FALSE(p.value().privateNamespace);
p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
AAPT_ASSERT_TRUE(p);
EXPECT_EQ(std::u16string(u"android"), p.value().package);
- EXPECT_EQ(true, p.value().privateNamespace);
+ EXPECT_TRUE(p.value().privateNamespace);
p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
AAPT_ASSERT_TRUE(p);
EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
- EXPECT_EQ(true, p.value().privateNamespace);
+ EXPECT_TRUE(p.value().privateNamespace);
p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
AAPT_ASSERT_TRUE(p);
EXPECT_EQ(std::u16string(), p.value().package);
- EXPECT_EQ(true, p.value().privateNamespace);
+ EXPECT_TRUE(p.value().privateNamespace);
}
} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index 01af669..381eb1f 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -15,8 +15,11 @@
*/
package android.view;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -64,4 +67,18 @@
thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
+
+ public static void dispose() {
+ try {
+ Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance");
+ threadInstanceField.setAccessible(true);
+ @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance =
+ (ThreadLocal<Choreographer>) threadInstanceField.get(null);
+ threadInstance.remove();
+ } catch (ReflectiveOperationException e) {
+ assert false;
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Unable to clear Choreographer memory.", e, null);
+ }
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 30512aa..ea9a255 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -44,6 +44,11 @@
private static final float PERPENDICULAR_ANGLE = 90f;
public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ Rect outline = new Rect();
+ if (!viewOutline.getRect(outline)) {
+ throw new IllegalArgumentException("Outline is not a rect shadow");
+ }
+
float shadowSize = elevationToShadow(elevation);
int saved = modifyCanvas(canvas, shadowSize);
if (saved == -1) {
@@ -54,8 +59,7 @@
cornerPaint.setStyle(Style.FILL);
Paint edgePaint = new Paint(cornerPaint);
edgePaint.setAntiAlias(false);
- Rect outline = viewOutline.mRect;
- float radius = viewOutline.mRadius;
+ float radius = viewOutline.getRadius();
float outerArcRadius = radius + shadowSize;
int[] colors = {START_COLOR, START_COLOR, END_COLOR};
cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index 51d32e3..23caaf8 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -64,7 +64,7 @@
private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
Outline outline) {
float elevation = getElevation(child, parent);
- if(outline.mRect != null) {
+ if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
RectShadowPainter.paintShadow(outline, elevation, canvas);
return;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index ce7104e..866b248 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -1453,6 +1453,7 @@
if (createdLooper) {
Bridge.cleanupThread();
+ Choreographer_Delegate.dispose();
}
}
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index d8ead23..0e788e0c 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
index 65d1dc5..bad296b 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
index 2da2cb9..adb58a3 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
@@ -197,6 +197,14 @@
android:inputType="numberPassword"
android:text="numeric password" />
+ <ToggleButton
+ android:id="@+id/toggleButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@id/editText4"
+ android:layout_toEndOf="@id/editText4"
+ android:text="New ToggleButton" />
+
<EditText
android:id="@id/editText5"
android:layout_width="wrap_content"
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 034c8b2..8f570ae 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -31,8 +31,8 @@
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
@@ -42,8 +42,12 @@
import com.android.utils.ILogger;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,9 +60,12 @@
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
+import com.google.android.collect.Lists;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -105,6 +112,21 @@
private static ILogger sLogger;
private static Bridge sBridge;
+ /** List of log messages generated by a render call. It can be used to find specific errors */
+ private static ArrayList<String> sRenderMessages = Lists.newArrayList();
+
+ @Rule
+ public static TestWatcher sRenderMessageWatcher = new TestWatcher() {
+ @Override
+ protected void succeeded(Description description) {
+ // We only check error messages if the rest of the test case was successful.
+ if (!sRenderMessages.isEmpty()) {
+ fail(description.getMethodName() + " render error message: " + sRenderMessages.get
+ (0));
+ }
+ }
+ };
+
static {
// Test that System Properties are properly set.
PLATFORM_DIR = getPlatformDir();
@@ -279,6 +301,11 @@
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
}
+ @Before
+ public void beforeTestCase() {
+ sRenderMessages.clear();
+ }
+
/** Test activity.xml */
@Test
public void testActivity() throws ClassNotFoundException {
@@ -289,6 +316,9 @@
@Test
public void testAllWidgets() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets.png");
+
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
@Test
@@ -299,6 +329,9 @@
@Test
public void testAllWidgetsTablet() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
private static void gc() {
@@ -649,6 +682,6 @@
}
private static void failWithMsg(@NonNull String msgFormat, Object... args) {
- fail(args == null ? "" : String.format(msgFormat, args));
+ sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
}
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1c7a308..0f84506 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -149,12 +149,11 @@
void setAllowScansWithTraffic(int enabled);
int getAllowScansWithTraffic();
- void setHalBasedAutojoinOffload(int enabled);
- int getHalBasedAutojoinOffload();
-
boolean enableAutoJoinWhenAssociated(boolean enabled);
boolean getEnableAutoJoinWhenAssociated();
+ void enableWifiConnectivityManager(boolean enabled);
+
WifiConnectionStatistics getConnectionStatistics();
void disableEphemeralNetwork(String SSID);
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 9e15d60..394934f 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -719,7 +719,7 @@
* Get CA certificates.
*/
@Nullable public X509Certificate[] getCaCertificates() {
- if (mCaCerts != null || mCaCerts.length > 0) {
+ if (mCaCerts != null && mCaCerts.length > 0) {
return mCaCerts;
} else {
return null;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8c1fbc3..6984fe2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -28,7 +28,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -43,7 +42,6 @@
import com.android.server.net.NetworkPinner;
import java.net.InetAddress;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -2721,25 +2719,14 @@
throw e.rethrowFromSystemServer();
}
}
- /**
- * Set setting for enabling autojoin Offload thru Wifi HAL layer
- * @hide
- */
- public void setHalBasedAutojoinOffload(int enabled) {
- try {
- mService.setHalBasedAutojoinOffload(enabled);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
/**
- * Get setting for enabling autojoin Offload thru Wifi HAL layer
+ * Enable/disable WifiConnectivityManager
* @hide
*/
- public int getHalBasedAutojoinOffload() {
+ public void enableWifiConnectivityManager(boolean enabled) {
try {
- return mService.getHalBasedAutojoinOffload();
+ mService.enableWifiConnectivityManager(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}