Merge "Add window setDecorView API."
diff --git a/Android.mk b/Android.mk
index 5713f3e..f0c0117 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,8 @@
core/java/android/app/IActivityContainerCallback.aidl \
core/java/android/app/IActivityController.aidl \
core/java/android/app/IActivityPendingResult.aidl \
+ core/java/android/app/IAlarmCompleteListener.aidl \
+ core/java/android/app/IAlarmListener.aidl \
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IAppTask.aidl \
core/java/android/app/ITaskStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index bee446b..3b4e3a9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3764,17 +3764,21 @@
public class AlarmManager {
method public void cancel(android.app.PendingIntent);
+ method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
+ method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(int, long, android.app.PendingIntent);
+ method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
method public void setTime(long);
method public void setTimeZone(java.lang.String);
method public void setWindow(int, long, long, android.app.PendingIntent);
+ method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3796,6 +3800,10 @@
field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
}
+ public static abstract interface AlarmManager.OnAlarmListener {
+ method public abstract void onAlarm();
+ }
+
public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
ctor protected AlertDialog(android.content.Context);
ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -4830,17 +4838,17 @@
field public android.app.Notification.Action[] actions;
field public android.media.AudioAttributes audioAttributes;
field public deprecated int audioStreamType;
- field public android.widget.RemoteViews bigContentView;
+ field public deprecated android.widget.RemoteViews bigContentView;
field public java.lang.String category;
field public int color;
field public android.app.PendingIntent contentIntent;
- field public android.widget.RemoteViews contentView;
+ field public deprecated android.widget.RemoteViews contentView;
field public int defaults;
field public android.app.PendingIntent deleteIntent;
field public android.os.Bundle extras;
field public int flags;
field public android.app.PendingIntent fullScreenIntent;
- field public android.widget.RemoteViews headsUpContentView;
+ field public deprecated android.widget.RemoteViews headsUpContentView;
field public deprecated int icon;
field public int iconLevel;
field public deprecated android.graphics.Bitmap largeIcon;
@@ -4931,14 +4939,22 @@
method public android.app.Notification.Builder extend(android.app.Notification.Extender);
method public android.os.Bundle getExtras();
method public deprecated android.app.Notification getNotification();
+ method public android.widget.RemoteViews makeBigContentView();
+ method public android.widget.RemoteViews makeContentView();
+ method public android.widget.RemoteViews makeHeadsUpContentView();
+ method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+ method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setCategory(java.lang.String);
method public android.app.Notification.Builder setColor(int);
- method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+ method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public 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);
+ method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+ method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+ method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
method public android.app.Notification.Builder setDefaults(int);
method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -5873,6 +5889,7 @@
method public java.lang.String getStructuredData();
method public android.net.Uri getWebUri();
method public boolean isAppProvidedIntent();
+ method public boolean isAppProvidedWebUri();
method public void setClipData(android.content.ClipData);
method public void setIntent(android.content.Intent);
method public void setStructuredData(java.lang.String);
@@ -9409,6 +9426,7 @@
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+ field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
@@ -22153,6 +22171,7 @@
field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+ field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -22176,6 +22195,7 @@
field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+ field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -22265,6 +22285,7 @@
field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+ field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -22310,6 +22331,7 @@
field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+ field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
@@ -36464,6 +36486,7 @@
method public boolean performHapticFeedback(int);
method public boolean performHapticFeedback(int, int);
method public boolean performLongClick();
+ method public boolean performLongClick(float, float);
method public void playSoundEffect(int);
method public boolean post(java.lang.Runnable);
method public boolean postDelayed(java.lang.Runnable, long);
@@ -39201,6 +39224,7 @@
method public abstract android.net.Uri getUrl();
method public abstract boolean hasGesture();
method public abstract boolean isForMainFrame();
+ method public abstract boolean isRedirect();
}
public class WebResourceResponse {
@@ -39563,7 +39587,8 @@
method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest);
method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
- method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+ method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+ method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest);
field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc
field public static final int ERROR_BAD_URL = -12; // 0xfffffff4
field public static final int ERROR_CONNECT = -6; // 0xfffffffa
diff --git a/api/system-current.txt b/api/system-current.txt
index 4648672..fd7d7a9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3873,18 +3873,22 @@
public class AlarmManager {
method public void cancel(android.app.PendingIntent);
+ method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
+ method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(int, long, android.app.PendingIntent);
+ method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
method public void setTime(long);
method public void setTimeZone(java.lang.String);
method public void setWindow(int, long, long, android.app.PendingIntent);
+ method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3906,6 +3910,10 @@
field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
}
+ public static abstract interface AlarmManager.OnAlarmListener {
+ method public abstract void onAlarm();
+ }
+
public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
ctor protected AlertDialog(android.content.Context);
ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -4947,17 +4955,17 @@
field public android.app.Notification.Action[] actions;
field public android.media.AudioAttributes audioAttributes;
field public deprecated int audioStreamType;
- field public android.widget.RemoteViews bigContentView;
+ field public deprecated android.widget.RemoteViews bigContentView;
field public java.lang.String category;
field public int color;
field public android.app.PendingIntent contentIntent;
- field public android.widget.RemoteViews contentView;
+ field public deprecated android.widget.RemoteViews contentView;
field public int defaults;
field public android.app.PendingIntent deleteIntent;
field public android.os.Bundle extras;
field public int flags;
field public android.app.PendingIntent fullScreenIntent;
- field public android.widget.RemoteViews headsUpContentView;
+ field public deprecated android.widget.RemoteViews headsUpContentView;
field public deprecated int icon;
field public int iconLevel;
field public deprecated android.graphics.Bitmap largeIcon;
@@ -5048,14 +5056,22 @@
method public android.app.Notification.Builder extend(android.app.Notification.Extender);
method public android.os.Bundle getExtras();
method public deprecated android.app.Notification getNotification();
+ method public android.widget.RemoteViews makeBigContentView();
+ method public android.widget.RemoteViews makeContentView();
+ method public android.widget.RemoteViews makeHeadsUpContentView();
+ method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification);
+ method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setCategory(java.lang.String);
method public android.app.Notification.Builder setColor(int);
- method public android.app.Notification.Builder setContent(android.widget.RemoteViews);
+ method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public 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);
+ method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews);
+ method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews);
+ method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
method public android.app.Notification.Builder setDefaults(int);
method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setExtras(android.os.Bundle);
@@ -6006,6 +6022,7 @@
method public java.lang.String getStructuredData();
method public android.net.Uri getWebUri();
method public boolean isAppProvidedIntent();
+ method public boolean isAppProvidedWebUri();
method public void setClipData(android.content.ClipData);
method public void setIntent(android.content.Intent);
method public void setStructuredData(java.lang.String);
@@ -9703,6 +9720,7 @@
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+ field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
@@ -24098,6 +24116,7 @@
field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259
field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5
field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305
+ field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff
field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff
field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb
field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe
@@ -24121,6 +24140,7 @@
field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef
field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f
field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43
+ field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2
field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2
field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400
field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314
@@ -24210,6 +24230,7 @@
field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51
field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52
field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50
+ field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20
field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000
field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6
field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2
@@ -24255,6 +24276,7 @@
field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064
field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066
field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a
+ field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1
field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4
field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5
field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f
@@ -38761,6 +38783,7 @@
method public boolean performHapticFeedback(int);
method public boolean performHapticFeedback(int, int);
method public boolean performLongClick();
+ method public boolean performLongClick(float, float);
method public void playSoundEffect(int);
method public boolean post(java.lang.Runnable);
method public boolean postDelayed(java.lang.Runnable, long);
@@ -41561,6 +41584,7 @@
method public abstract android.net.Uri getUrl();
method public abstract boolean hasGesture();
method public abstract boolean isForMainFrame();
+ method public abstract boolean isRedirect();
}
public class WebResourceResponse {
@@ -41970,7 +41994,8 @@
method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest);
method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
- method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+ method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
+ method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest);
field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc
field public static final int ERROR_BAD_URL = -12; // 0xfffffff4
field public static final int ERROR_CONNECT = -6; // 0xfffffffa
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3f8e311..2960cdc 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -283,7 +283,7 @@
} else if (args.length == 2) {
if (args[0].equalsIgnoreCase("-p")) {
validCommand = true;
- return displayPackageFilePath(args[1], UserHandle.USER_OWNER);
+ return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
}
}
return 1;
@@ -767,7 +767,7 @@
}
private int runPath() {
- int userId = UserHandle.USER_OWNER;
+ int userId = UserHandle.USER_SYSTEM;
String option = nextOption();
if (option != null && option.equals("--user")) {
String optionData = nextOptionData();
@@ -1650,7 +1650,7 @@
}
private int runClear() {
- int userId = UserHandle.USER_OWNER;
+ int userId = UserHandle.USER_SYSTEM;
String option = nextOption();
if (option != null && option.equals("--user")) {
String optionData = nextOptionData();
@@ -1722,7 +1722,7 @@
}
private int runSetEnabledSetting(int state) {
- int userId = UserHandle.USER_OWNER;
+ int userId = UserHandle.USER_SYSTEM;
String option = nextOption();
if (option != null && option.equals("--user")) {
String optionData = nextOptionData();
@@ -1771,7 +1771,7 @@
}
private int runSetHiddenSetting(boolean state) {
- int userId = UserHandle.USER_OWNER;
+ int userId = UserHandle.USER_SYSTEM;
String option = nextOption();
if (option != null && option.equals("--user")) {
String optionData = nextOptionData();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6ae32d0..fce1b2e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1200,6 +1200,12 @@
public int id;
/**
+ * The stack that currently contains this task.
+ * @hide
+ */
+ public int stackId;
+
+ /**
* The component launched as the first activity in the task. This can
* be considered the "application" of this task.
*/
@@ -1248,6 +1254,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
+ dest.writeInt(stackId);
ComponentName.writeToParcel(baseActivity, dest);
ComponentName.writeToParcel(topActivity, dest);
if (thumbnail != null) {
@@ -1264,6 +1271,7 @@
public void readFromParcel(Parcel source) {
id = source.readInt();
+ stackId = source.readInt();
baseActivity = ComponentName.readFromParcel(source);
topActivity = ComponentName.readFromParcel(source);
if (source.readInt() != 0) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 70f1bcc..da743f8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -116,21 +116,22 @@
static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
String tag) {
try {
- getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg, tag);
+ getDefault().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+ sourceUid, sourcePkg, tag);
} catch (RemoteException ex) {
}
}
static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
try {
- getDefault().noteAlarmStart(ps.getTarget(), sourceUid, tag);
+ getDefault().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
} catch (RemoteException ex) {
}
}
static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
try {
- getDefault().noteAlarmFinish(ps.getTarget(), sourceUid, tag);
+ getDefault().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
} catch (RemoteException ex) {
}
}
@@ -2680,19 +2681,10 @@
case REPORT_SIZE_CONFIGURATIONS: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- int horizontalSize = data.readInt();
- int[] horizontal = null;
- if (horizontalSize > 0) {
- horizontal = new int[horizontalSize];
- data.readIntArray(horizontal);
- }
- int[] vertical = null;
- int verticalSize = data.readInt();
- if (verticalSize > 0) {
- vertical = new int[verticalSize];
- data.readIntArray(vertical);
- }
- reportSizeConfigurations(token, horizontal, vertical);
+ int[] horizontal = readIntArray(data);
+ int[] vertical = readIntArray(data);
+ int[] smallest = readIntArray(data);
+ reportSizeConfigurations(token, horizontal, vertical, smallest);
return true;
}
case SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION: {
@@ -2714,6 +2706,16 @@
return super.onTransact(code, data, reply, flags);
}
+ private int[] readIntArray(Parcel data) {
+ int[] smallest = null;
+ int smallestSize = data.readInt();
+ if (smallestSize > 0) {
+ smallest = new int[smallestSize];
+ data.readIntArray(smallest);
+ }
+ return smallest;
+ }
+
public IBinder asBinder() {
return this;
}
@@ -6240,29 +6242,30 @@
@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations) throws RemoteException {
+ int[] verticalSizeConfigurations, int[] smallestSizeConfigurations)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
- if (horizontalSizeConfiguration == null) {
- data.writeInt(0);
- } else {
- data.writeInt(horizontalSizeConfiguration.length);
- data.writeIntArray(horizontalSizeConfiguration);
- }
- if (verticalSizeConfigurations == null) {
- data.writeInt(0);
- } else {
- data.writeInt(verticalSizeConfigurations.length);
- data.writeIntArray(verticalSizeConfigurations);
- }
+ writeIntArray(horizontalSizeConfiguration, data);
+ writeIntArray(verticalSizeConfigurations, data);
+ writeIntArray(smallestSizeConfigurations, data);
mRemote.transact(REPORT_SIZE_CONFIGURATIONS, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
+ private static void writeIntArray(int[] array, Parcel data) {
+ if (array == null) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(array.length);
+ data.writeIntArray(array);
+ }
+ }
+
@Override
public void suppressResizeConfigChanges(boolean suppress) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 933f98d..0266a37 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -23,10 +25,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Pair;
import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
import android.view.View;
import android.view.Window;
@@ -127,6 +131,17 @@
public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
/**
+ * Descriptions of app transition animations to be played during the activity launch.
+ */
+ private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
+
+ /**
+ * Where the docked stack should be positioned.
+ * @hide
+ */
+ private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+
+ /**
* For Activity transitions, the calling Activity's TransitionListener used to
* notify the called Activity when the shared element and the exit transitions
* complete.
@@ -190,6 +205,8 @@
private int mResultCode;
private int mExitCoordinatorIndex;
private PendingIntent mUsageTimeReport;
+ private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ private AppTransitionAnimationSpec mAnimSpecs[];
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -503,6 +520,18 @@
return opts;
}
+ /** @hide */
+ public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+ AppTransitionAnimationSpec[] specs, Handler handler,
+ OnAnimationStartedListener listener) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+ opts.mAnimSpecs = specs;
+ opts.setOnAnimationStartedListener(handler, listener);
+ return opts;
+ }
+
/**
* Create an ActivityOptions to transition between Activities using cross-Activity scene
* animations. This method carries the position of one shared element to the started Activity.
@@ -688,6 +717,14 @@
mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
break;
}
+ mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ if (opts.containsKey(KEY_ANIM_SPECS)) {
+ Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
+ mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
+ for (int i = specs.length - 1; i >= 0; i--) {
+ mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i];
+ }
+ }
}
/** @hide */
@@ -800,12 +837,23 @@
}
/** @hide */
+ public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
+
+ /** @hide */
public static void abort(Bundle options) {
if (options != null) {
(new ActivityOptions(options)).abort();
}
}
+ /** @hide */
+ public int getDockCreateMode() { return mDockCreateMode; }
+
+ /** @hide */
+ public void setDockCreateMode(int dockCreateMode) {
+ mDockCreateMode = dockCreateMode;
+ }
+
/**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
@@ -880,6 +928,7 @@
mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
+ mAnimSpecs = otherOptions.mAnimSpecs;
}
/**
@@ -945,6 +994,10 @@
b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
+ b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+ if (mAnimSpecs != null) {
+ b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
+ }
return b;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b9292de..a74f528 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -88,6 +88,7 @@
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.util.SuperNotCalledException;
import android.view.Display;
import android.view.HardwareRenderer;
@@ -2638,32 +2639,24 @@
if (configurations == null) {
return;
}
- IntArray horizontal = new IntArray();
- IntArray vertical = new IntArray();
+ SparseIntArray horizontal = new SparseIntArray();
+ SparseIntArray vertical = new SparseIntArray();
+ SparseIntArray smallest = new SparseIntArray();
for (int i = configurations.length - 1; i >= 0; i--) {
Configuration config = configurations[i];
if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- vertical.add(config.screenHeightDp);
+ vertical.put(config.screenHeightDp, 0);
}
if (config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- horizontal.add(config.screenWidthDp);
+ horizontal.put(config.screenWidthDp, 0);
}
if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- vertical.add(config.smallestScreenWidthDp);
- horizontal.add(config.smallestScreenWidthDp);
+ smallest.put(config.smallestScreenWidthDp, 0);
}
}
- int[] horizontalArray = null;
- if (horizontal.size() > 0) {
- horizontalArray = horizontal.toArray();
- }
- int[] verticalArray = null;
- if (vertical.size() > 0) {
- verticalArray = vertical.toArray();
- }
try {
- ActivityManagerNative.getDefault().reportSizeConfigurations(r.token, horizontalArray,
- verticalArray);
+ ActivityManagerNative.getDefault().reportSizeConfigurations(r.token,
+ horizontal.copyKeys(), vertical.copyKeys(), smallest.copyKeys());
} catch (RemoteException ex) {
}
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index fb03b62..3dc9582 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -21,15 +21,21 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
+import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
+import android.util.Log;
+
import libcore.util.ZoneInfoDB;
import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
/**
* This class provides access to the system alarm services. These allow you
@@ -72,6 +78,8 @@
* Context.getSystemService(Context.ALARM_SERVICE)}.
*/
public class AlarmManager {
+ private static final String TAG = "AlarmManager";
+
/**
* Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
* (wall clock time in UTC), which will wake up the device when
@@ -160,9 +168,89 @@
public static final int FLAG_IDLE_UNTIL = 1<<4;
private final IAlarmManager mService;
+ private final String mPackageName;
private final boolean mAlwaysExact;
private final int mTargetSdkVersion;
+ private final Handler mMainThreadHandler;
+ /**
+ * Direct-notification alarms: the requester must be running continuously from the
+ * time the alarm is set to the time it is delivered, or delivery will fail. Only
+ * one-shot alarms can be set using this mechanism, not repeating alarms.
+ */
+ public interface OnAlarmListener {
+ /**
+ * Callback method that is invoked by the system when the alarm time is reached.
+ */
+ public void onAlarm();
+ }
+
+ final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+ final OnAlarmListener mListener;
+ Handler mHandler;
+ IAlarmCompleteListener mCompletion;
+
+ public ListenerWrapper(OnAlarmListener listener) {
+ mListener = listener;
+ }
+
+ public void setHandler(Handler h) {
+ mHandler = h;
+ }
+
+ public void cancel() {
+ try {
+ mService.remove(null, this);
+ } catch (RemoteException ex) {
+ }
+
+ synchronized (AlarmManager.class) {
+ if (sWrappers != null) {
+ sWrappers.remove(mListener);
+ }
+ }
+ }
+
+ @Override
+ public void doAlarm(IAlarmCompleteListener alarmManager) {
+ mCompletion = alarmManager;
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ // Remove this listener from the wrapper cache first; the server side
+ // already considers it gone
+ synchronized (AlarmManager.class) {
+ if (sWrappers != null) {
+ sWrappers.remove(mListener);
+ }
+ }
+
+ // Now deliver it to the app
+ try {
+ mListener.onAlarm();
+ } finally {
+ // No catch -- make sure to report completion to the system process,
+ // but continue to allow the exception to crash the app.
+
+ try {
+ mCompletion.alarmComplete(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+ }
+ }
+ }
+ }
+
+ // Tracking of the OnAlarmListener -> wrapper mapping, for cancel() support.
+ // Access is synchronized on the AlarmManager class object.
+ //
+ // These are weak references so that we don't leak listener references if, for
+ // example, the pending-alarm messages are posted to a HandlerThread that is
+ // disposed of prior to alarm delivery. The underlying messages will be GC'd
+ // but this static reference would still persist, orphaned, never deallocated.
+ private static WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>> sWrappers;
/**
* package private on purpose
@@ -170,8 +258,10 @@
AlarmManager(IAlarmManager service, Context ctx) {
mService = service;
+ mPackageName = ctx.getPackageName();
mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+ mMainThreadHandler = new Handler(ctx.getMainLooper());
}
private long legacyExactLength() {
@@ -249,7 +339,37 @@
* @see #RTC_WAKEUP
*/
public void set(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+ null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, PendingIntent)}. Rather than
+ * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP},
+ * {@link #RTC}, or {@link #RTC_WAKEUP}.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param tag string describing the alarm, used for logging and battery-use
+ * attribution
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+ * callback, or {@code null} to run that callback on the main looper.
+ */
+ public void set(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+ Handler targetHandler) {
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -310,8 +430,8 @@
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
- null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+ null, null, null, null, null);
}
/**
@@ -361,7 +481,23 @@
*/
public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
- setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+ null, null, null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ */
+ public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+ String tag, OnAlarmListener listener, Handler targetHandler) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -399,7 +535,23 @@
* @see #RTC_WAKEUP
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+ null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setExact(int, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ */
+ public void setExact(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+ Handler targetHandler) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -408,7 +560,8 @@
* @hide
*/
public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation,
+ null, null, null, null, null);
}
/**
@@ -436,19 +589,38 @@
* @see android.content.Intent#filterEquals
*/
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+ null, null, null, null, info);
}
/** @hide */
@SystemApi
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
- null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+ null, workSource, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+ * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @hide
+ */
+ //@SystemApi
+ public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
+ OnAlarmListener listener, Handler targetHandler, WorkSource workSource) {
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
+ targetHandler, workSource, null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
- int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+ int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag,
+ Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -460,9 +632,30 @@
triggerAtMillis = 0;
}
+ ListenerWrapper recipientWrapper = null;
+ if (listener != null) {
+ synchronized (AlarmManager.class) {
+ if (sWrappers == null) {
+ sWrappers = new WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>>();
+ }
+
+ WeakReference<ListenerWrapper> wrapperRef = sWrappers.get(listener);
+ // no existing wrapper *or* we've lost our weak ref to it => build a new one
+ if (wrapperRef == null ||
+ (recipientWrapper = wrapperRef.get()) == null) {
+ recipientWrapper = new ListenerWrapper(listener);
+ wrapperRef = new WeakReference<ListenerWrapper>(recipientWrapper);
+ sWrappers.put(listener, wrapperRef);
+ }
+ }
+
+ final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+ recipientWrapper.setHandler(handler);
+ }
+
try {
- mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
- workSource, alarmClock);
+ mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+ operation, recipientWrapper, listenerTag, workSource, alarmClock);
} catch (RemoteException ex) {
}
}
@@ -562,7 +755,8 @@
*/
public void setInexactRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+ null, null, null, null);
}
/**
@@ -611,8 +805,8 @@
* @see #RTC_WAKEUP
*/
public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+ operation, null, null, null, null, null);
}
/**
@@ -666,7 +860,7 @@
*/
public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null);
+ null, null, null, null, null);
}
/**
@@ -680,13 +874,44 @@
* @see #set
*/
public void cancel(PendingIntent operation) {
+ if (operation == null) {
+ throw new NullPointerException("operation");
+ }
+
try {
- mService.remove(operation);
+ mService.remove(operation, null);
} catch (RemoteException ex) {
}
}
/**
+ * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+ *
+ * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+ */
+ public void cancel(OnAlarmListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener");
+ }
+
+ ListenerWrapper wrapper = null;
+ synchronized (AlarmManager.class) {
+ final WeakReference<ListenerWrapper> wrapperRef;
+ wrapperRef = sWrappers.get(listener);
+ if (wrapperRef != null) {
+ wrapper = wrapperRef.get();
+ }
+ }
+
+ if (wrapper == null) {
+ Log.w(TAG, "Unrecognized alarm listener " + listener);
+ return;
+ }
+
+ wrapper.cancel();
+ }
+
+ /**
* Set the system wall clock time.
* Requires the permission android.permission.SET_TIME.
*
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index ebdd302..fcc040b3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -123,7 +123,8 @@
public void activitySlept(IBinder token) throws RemoteException;
public void activityDestroyed(IBinder token) throws RemoteException;
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations) throws RemoteException;
+ int[] verticalSizeConfigurations, int[] smallestWidthConfigurations)
+ throws RemoteException;
public String getCallingPackage(IBinder token) throws RemoteException;
public ComponentName getCallingActivity(IBinder token) throws RemoteException;
public List<IAppTask> getAppTasks(String callingPackage) throws RemoteException;
diff --git a/core/java/android/app/IAlarmCompleteListener.aidl b/core/java/android/app/IAlarmCompleteListener.aidl
new file mode 100644
index 0000000..9f9ee40
--- /dev/null
+++ b/core/java/android/app/IAlarmCompleteListener.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.os.IBinder;
+
+/**
+ * Callback from app into system process to indicate that processing of
+ * a direct-call alarm has completed.
+ * {@hide}
+ */
+interface IAlarmCompleteListener {
+ void alarmComplete(in IBinder who);
+}
diff --git a/core/java/android/app/IAlarmListener.aidl b/core/java/android/app/IAlarmListener.aidl
new file mode 100644
index 0000000..a110d4d
--- /dev/null
+++ b/core/java/android/app/IAlarmListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.app.IAlarmCompleteListener;
+
+/**
+ * System private API for direct alarm callbacks (non-broadcast deliver). See the
+ * AlarmManager#set() variants that take an AlarmReceiver callback object
+ * rather than a PendingIntent.
+ *
+ * {@hide}
+ */
+oneway interface IAlarmListener {
+ void doAlarm(in IAlarmCompleteListener callback);
+}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 327c00b..7b05b49 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -17,7 +17,9 @@
package android.app;
import android.app.AlarmManager;
+import android.app.IAlarmListener;
import android.app.PendingIntent;
+import android.content.Intent;
import android.os.WorkSource;
/**
@@ -27,14 +29,12 @@
*/
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
- void set(int type, long triggerAtTime, long windowLength,
- long interval, int flags, in PendingIntent operation, in WorkSource workSource,
- in AlarmManager.AlarmClockInfo alarmClock);
+ void set(String callingPackage, int type, long triggerAtTime, long windowLength,
+ long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
+ String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
- void remove(in PendingIntent operation);
+ void remove(in PendingIntent operation, in IAlarmListener listener);
long getNextWakeFromIdleTime();
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
}
-
-
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e95a35a..30232da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -83,10 +83,8 @@
int getZenMode();
ZenModeConfig getZenModeConfig();
- boolean setZenModeConfig(in ZenModeConfig config, String reason);
oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
- oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index db18722..49edff4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -251,15 +251,26 @@
public RemoteViews tickerView;
/**
- * The view that will represent this notification in the expanded status bar.
+ * The view that will represent this notification in the notification list (which is pulled
+ * down from the status bar).
+ *
+ * As of N, this field is not used. The notification view is determined by the inputs to
+ * {@link Notification.Builder}; a custom RemoteViews can optionally be
+ * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
*/
+ @Deprecated
public RemoteViews contentView;
/**
* A large-format version of {@link #contentView}, giving the Notification an
* opportunity to show more detail. The system UI may choose to show this
* instead of the normal content view at its discretion.
+ *
+ * As of N, this field is not used. The expanded notification view is determined by the
+ * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+ * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
*/
+ @Deprecated
public RemoteViews bigContentView;
@@ -268,7 +279,12 @@
* opportunity to add action buttons to contentView. At its discretion, the system UI may
* choose to show this as a heads-up notification, which will pop up so the user can see
* it without leaving their current activity.
+ *
+ * As of N, this field is not used. The heads-up notification view is determined by the
+ * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
+ * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
*/
+ @Deprecated
public RemoteViews headsUpContentView;
/**
@@ -867,6 +883,11 @@
public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
/**
+ * @hide
+ */
+ public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
+
+ /**
* Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
* displayed in the heads up space.
*
@@ -1707,8 +1728,6 @@
extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
extras.remove(Notification.EXTRA_PICTURE);
extras.remove(Notification.EXTRA_BIG_TEXT);
- // Prevent light notifications from being rebuilt.
- extras.remove(Builder.EXTRA_NEEDS_REBUILD);
}
}
@@ -1901,21 +1920,13 @@
@Deprecated
public void setLatestEventInfo(Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
- Notification.Builder builder = new Notification.Builder(context);
+ if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){
+ Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.",
+ new Throwable());
+ }
- // First, ensure that key pieces of information that may have been set directly
- // are preserved
- builder.setWhen(this.when);
- builder.setSmallIcon(this.icon);
- builder.setPriority(this.priority);
- builder.setTicker(this.tickerText);
- builder.setNumber(this.number);
- builder.setColor(this.color);
- builder.mFlags = this.flags;
- builder.setSound(this.sound, this.audioStreamType);
- builder.setDefaults(this.defaults);
- builder.setVibrate(this.vibrate);
- builder.setDeleteIntent(this.deleteIntent);
+ // ensure that any information already set directly is preserved
+ final Notification.Builder builder = new Notification.Builder(context, this);
// now apply the latestEventInfo fields
if (contentTitle != null) {
@@ -1925,7 +1936,8 @@
builder.setContentText(contentText);
}
builder.setContentIntent(contentIntent);
- builder.buildInto(this);
+
+ builder.build(); // callers expect this notification to be ready to use
}
@Override
@@ -2080,15 +2092,6 @@
/**
* @hide
*/
- public boolean isValid() {
- // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that
- // for legacy reasons.
- return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW);
- }
-
- /**
- * @hide
- */
public boolean isGroupSummary() {
return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0;
}
@@ -2125,99 +2128,14 @@
private static final int MAX_ACTION_BUTTONS = 3;
private static final float LARGE_TEXT_SCALE = 1.3f;
- /**
- * @hide
- */
- public static final String EXTRA_NEEDS_REBUILD = "android.rebuild";
-
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
- "android.rebuild.contentViewActionCount";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW
- = "android.rebuild.bigView";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
- = "android.rebuild.bigViewActionCount";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW
- = "android.rebuild.hudView";
- /**
- * @hide
- */
- public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
- = "android.rebuild.hudViewActionCount";
-
- /**
- * The ApplicationInfo of the package that created the notification, used to create
- * a context to rebuild the notification via a Builder.
- * @hide
- */
- private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO =
- "android.rebuild.applicationInfo";
-
- // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of
- // memory intensive resources.
- private static final boolean STRIP_AND_REBUILD = true;
-
private Context mContext;
-
- private long mWhen;
- private Icon mSmallIcon, mLargeIcon;
- private int mSmallIconLevel;
- private int mNumber;
- private CharSequence mContentTitle;
- private CharSequence mContentText;
- private CharSequence mContentInfo;
- private CharSequence mSubText;
- private PendingIntent mContentIntent;
- private RemoteViews mContentView;
- private PendingIntent mDeleteIntent;
- private PendingIntent mFullScreenIntent;
- private CharSequence mTickerText;
- private RemoteViews mTickerView;
- private Uri mSound;
- private int mAudioStreamType;
- private AudioAttributes mAudioAttributes;
- private long[] mVibrate;
- private int mLedArgb;
- private int mLedOnMs;
- private int mLedOffMs;
- private int mDefaults;
- private int mFlags;
- private int mProgressMax;
- private int mProgress;
- private boolean mProgressIndeterminate;
- private String mCategory;
- private String mGroupKey;
- private String mSortKey;
- private Bundle mExtras;
- private int mPriority;
- private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
- private boolean mUseChronometer;
+ private Notification mN;
+ private Bundle mUserExtras = new Bundle();
private Style mStyle;
- private boolean mShowWhen = true;
- private int mVisibility = VISIBILITY_PRIVATE;
- private Notification mPublicVersion = null;
- private final NotificationColorUtil mColorUtil;
- private ArrayList<String> mPeople;
- private int mColor = COLOR_DEFAULT;
+ private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
+ private ArrayList<String> mPersonList = new ArrayList<String>();
+ private NotificationColorUtil mColorUtil;
+ private boolean mColorUtilInited = false;
private List<Topic> mTopics = new ArrayList<>();
/**
@@ -2226,25 +2144,6 @@
private int mOriginatingUserId;
/**
- * Contains extras related to rebuilding during the build phase.
- */
- private Bundle mRebuildBundle = new Bundle();
- /**
- * Contains the notification to rebuild when this Builder is in "rebuild" mode.
- * Null otherwise.
- */
- private Notification mRebuildNotification = null;
-
- /**
- * Whether the build notification has three lines. This is used to make the top padding for
- * both the contracted and expanded layout consistent.
- *
- * <p>
- * This field is only valid during the build phase.
- */
- private boolean mHasThreeLines;
-
- /**
* Constructs a new Builder with the defaults:
*
@@ -2264,61 +2163,67 @@
* object.
*/
public Builder(Context context) {
- /*
- * Important compatibility note!
- * Some apps out in the wild create a Notification.Builder in their Activity subclass
- * constructor for later use. At this point Activities - themselves subclasses of
- * ContextWrapper - do not have their inner Context populated yet. This means that
- * any calls to Context methods from within this constructor can cause NPEs in existing
- * apps. Any data populated from mContext should therefore be populated lazily to
- * preserve compatibility.
- */
- mContext = context;
-
- // Set defaults to match the defaults of a Notification
- mWhen = System.currentTimeMillis();
- mAudioStreamType = STREAM_DEFAULT;
- mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
- mPriority = PRIORITY_DEFAULT;
- mPeople = new ArrayList<String>();
-
- mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ?
- NotificationColorUtil.getInstance(mContext) : null;
+ this(context, null);
}
/**
- * Creates a Builder for rebuilding the given Notification.
- * <p>
- * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'.
+ * @hide
*/
- private Builder(Context context, Notification n) {
- this(context);
- mRebuildNotification = n;
- restoreFromNotification(n);
+ public Builder(Context context, Notification toAdopt) {
+ mContext = context;
- Style style = null;
- Bundle extras = n.extras;
- String templateClass = extras.getString(EXTRA_TEMPLATE);
- if (!TextUtils.isEmpty(templateClass)) {
- Class<? extends Style> styleClass = getNotificationStyleClass(templateClass);
- if (styleClass == null) {
- Log.d(TAG, "Unknown style class: " + styleClass);
- return;
+ if (toAdopt == null) {
+ mN = new Notification();
+ mN.extras.putBoolean(EXTRA_SHOW_WHEN, true);
+ mN.priority = PRIORITY_DEFAULT;
+ mN.visibility = VISIBILITY_PRIVATE;
+ } else {
+ mN = toAdopt;
+ if (mN.actions != null) {
+ Collections.addAll(mActions, mN.actions);
}
- try {
- Constructor<? extends Style> constructor = styleClass.getConstructor();
- constructor.setAccessible(true);
- style = constructor.newInstance();
- style.restoreFromExtras(extras);
- } catch (Throwable t) {
- Log.e(TAG, "Could not create Style", t);
- return;
+ if (mN.extras.containsKey(EXTRA_PEOPLE)) {
+ Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
+ }
+
+ if (mN.getTopics() != null) {
+ Collections.addAll(mTopics, mN.getTopics());
+ }
+
+ String templateClass = mN.extras.getString(EXTRA_TEMPLATE);
+ if (!TextUtils.isEmpty(templateClass)) {
+ final Class<? extends Style> styleClass
+ = getNotificationStyleClass(templateClass);
+ if (styleClass == null) {
+ Log.d(TAG, "Unknown style class: " + templateClass);
+ } else {
+ try {
+ final Constructor<? extends Style> ctor = styleClass.getConstructor();
+ ctor.setAccessible(true);
+ final Style style = ctor.newInstance();
+ style.restoreFromExtras(mN.extras);
+
+ if (style != null) {
+ setStyle(style);
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, "Could not create Style", t);
+ }
+ }
+ }
+
+ }
+ }
+
+ private NotificationColorUtil getColorUtil() {
+ if (!mColorUtilInited) {
+ mColorUtilInited = true;
+ if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+ mColorUtil = NotificationColorUtil.getInstance(mContext);
}
}
- if (style != null) {
- setStyle(style);
- }
+ return mColorUtil;
}
/**
@@ -2329,7 +2234,7 @@
* @see Notification#when
*/
public Builder setWhen(long when) {
- mWhen = when;
+ mN.when = when;
return this;
}
@@ -2338,7 +2243,7 @@
* in the content view.
*/
public Builder setShowWhen(boolean show) {
- mShowWhen = show;
+ mN.extras.putBoolean(EXTRA_SHOW_WHEN, show);
return this;
}
@@ -2354,7 +2259,7 @@
* @see Notification#when
*/
public Builder setUsesChronometer(boolean b) {
- mUseChronometer = b;
+ mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b);
return this;
}
@@ -2390,7 +2295,7 @@
* @see Notification#iconLevel
*/
public Builder setSmallIcon(@DrawableRes int icon, int level) {
- mSmallIconLevel = level;
+ mN.iconLevel = level;
return setSmallIcon(icon);
}
@@ -2403,7 +2308,10 @@
* @see Notification#icon
*/
public Builder setSmallIcon(Icon icon) {
- mSmallIcon = icon;
+ mN.setSmallIcon(icon);
+ if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
+ mN.icon = icon.getResId();
+ }
return this;
}
@@ -2411,7 +2319,7 @@
* Set the first line of text in the platform notification template.
*/
public Builder setContentTitle(CharSequence title) {
- mContentTitle = safeCharSequence(title);
+ mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title));
return this;
}
@@ -2419,7 +2327,7 @@
* Set the second line of text in the platform notification template.
*/
public Builder setContentText(CharSequence text) {
- mContentText = safeCharSequence(text);
+ mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text));
return this;
}
@@ -2429,7 +2337,7 @@
* same location in the standard template.
*/
public Builder setSubText(CharSequence text) {
- mSubText = safeCharSequence(text);
+ mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text));
return this;
}
@@ -2439,7 +2347,7 @@
* font size for readability.
*/
public Builder setNumber(int number) {
- mNumber = number;
+ mN.number = number;
return this;
}
@@ -2450,7 +2358,7 @@
* right (to the right of a smallIcon if it has been placed there).
*/
public Builder setContentInfo(CharSequence info) {
- mContentInfo = safeCharSequence(info);
+ mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info));
return this;
}
@@ -2460,19 +2368,52 @@
* The platform template will represent this using a {@link ProgressBar}.
*/
public Builder setProgress(int max, int progress, boolean indeterminate) {
- mProgressMax = max;
- mProgress = progress;
- mProgressIndeterminate = indeterminate;
+ mN.extras.putInt(EXTRA_PROGRESS, progress);
+ mN.extras.putInt(EXTRA_PROGRESS_MAX, max);
+ mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate);
return this;
}
/**
* Supply a custom RemoteViews to use instead of the platform template.
*
- * @see Notification#contentView
+ * Use {@link #setCustomContentView(RemoteViews)} instead.
*/
+ @Deprecated
public Builder setContent(RemoteViews views) {
- mContentView = views;
+ return setCustomContentView(views);
+ }
+
+ /**
+ * Supply custom RemoteViews to use instead of the platform template.
+ *
+ * This will override the layout that would otherwise be constructed by this Builder
+ * object.
+ */
+ public Builder setCustomContentView(RemoteViews contentView) {
+ mN.contentView = contentView;
+ return this;
+ }
+
+ /**
+ * Supply custom RemoteViews to use instead of the platform template in the expanded form.
+ *
+ * This will override the expanded layout that would otherwise be constructed by this
+ * Builder object.
+ */
+ public Builder setCustomBigContentView(RemoteViews contentView) {
+ mN.bigContentView = contentView;
+ return this;
+ }
+
+ /**
+ * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
+ *
+ * This will override the heads-up layout that would otherwise be constructed by this
+ * Builder object.
+ */
+ public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
+ mN.headsUpContentView = contentView;
return this;
}
@@ -2488,7 +2429,7 @@
* @see Notification#contentIntent Notification.contentIntent
*/
public Builder setContentIntent(PendingIntent intent) {
- mContentIntent = intent;
+ mN.contentIntent = intent;
return this;
}
@@ -2498,7 +2439,7 @@
* @see Notification#deleteIntent
*/
public Builder setDeleteIntent(PendingIntent intent) {
- mDeleteIntent = intent;
+ mN.deleteIntent = intent;
return this;
}
@@ -2523,7 +2464,7 @@
* @see Notification#fullScreenIntent
*/
public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
- mFullScreenIntent = intent;
+ mN.fullScreenIntent = intent;
setFlag(FLAG_HIGH_PRIORITY, highPriority);
return this;
}
@@ -2534,7 +2475,7 @@
* @see Notification#tickerText
*/
public Builder setTicker(CharSequence tickerText) {
- mTickerText = safeCharSequence(tickerText);
+ mN.tickerText = safeCharSequence(tickerText);
return this;
}
@@ -2544,8 +2485,8 @@
*/
@Deprecated
public Builder setTicker(CharSequence tickerText, RemoteViews views) {
- mTickerText = safeCharSequence(tickerText);
- mTickerView = views; // we'll save it for you anyway
+ setTicker(tickerText);
+ // views is ignored
return this;
}
@@ -2568,7 +2509,8 @@
* badge atop the large icon).
*/
public Builder setLargeIcon(Icon icon) {
- mLargeIcon = icon;
+ mN.mLargeIcon = icon;
+ mN.extras.putParcelable(EXTRA_LARGE_ICON, icon);
return this;
}
@@ -2585,8 +2527,8 @@
* @see Notification#sound
*/
public Builder setSound(Uri sound) {
- mSound = sound;
- mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
+ mN.sound = sound;
+ mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT;
return this;
}
@@ -2603,8 +2545,8 @@
*/
@Deprecated
public Builder setSound(Uri sound, int streamType) {
- mSound = sound;
- mAudioStreamType = streamType;
+ mN.sound = sound;
+ mN.audioStreamType = streamType;
return this;
}
@@ -2619,8 +2561,8 @@
* @see Notification#sound
*/
public Builder setSound(Uri sound, AudioAttributes audioAttributes) {
- mSound = sound;
- mAudioAttributes = audioAttributes;
+ mN.sound = sound;
+ mN.audioAttributes = audioAttributes;
return this;
}
@@ -2637,7 +2579,7 @@
* @see Notification#vibrate
*/
public Builder setVibrate(long[] pattern) {
- mVibrate = pattern;
+ mN.vibrate = pattern;
return this;
}
@@ -2654,9 +2596,9 @@
* @see Notification#ledOffMS
*/
public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
- mLedArgb = argb;
- mLedOnMs = onMs;
- mLedOffMs = offMs;
+ mN.ledARGB = argb;
+ mN.ledOnMS = onMs;
+ mN.ledOffMS = offMs;
return this;
}
@@ -2724,7 +2666,7 @@
* For all default values, use {@link #DEFAULT_ALL}.
*/
public Builder setDefaults(int defaults) {
- mDefaults = defaults;
+ mN.defaults = defaults;
return this;
}
@@ -2734,7 +2676,7 @@
* @see Notification#priority
*/
public Builder setPriority(@Priority int pri) {
- mPriority = pri;
+ mN.priority = pri;
return this;
}
@@ -2744,7 +2686,7 @@
* @see Notification#category
*/
public Builder setCategory(String category) {
- mCategory = category;
+ mN.category = category;
return this;
}
@@ -2771,7 +2713,7 @@
* @see Notification#EXTRA_PEOPLE
*/
public Builder addPerson(String uri) {
- mPeople.add(uri);
+ mPersonList.add(uri);
return this;
}
@@ -2787,7 +2729,7 @@
* @return this object for method chaining
*/
public Builder setGroup(String groupKey) {
- mGroupKey = groupKey;
+ mN.mGroupKey = groupKey;
return this;
}
@@ -2816,7 +2758,7 @@
* @see String#compareTo(String)
*/
public Builder setSortKey(String sortKey) {
- mSortKey = sortKey;
+ mN.mSortKey = sortKey;
return this;
}
@@ -2829,11 +2771,7 @@
*/
public Builder addExtras(Bundle extras) {
if (extras != null) {
- if (mExtras == null) {
- mExtras = new Bundle(extras);
- } else {
- mExtras.putAll(extras);
- }
+ mUserExtras.putAll(extras);
}
return this;
}
@@ -2851,7 +2789,9 @@
* @see Notification#extras
*/
public Builder setExtras(Bundle extras) {
- mExtras = extras;
+ if (extras != null) {
+ mUserExtras = extras;
+ }
return this;
}
@@ -2866,10 +2806,13 @@
* @see Notification#extras
*/
public Bundle getExtras() {
- if (mExtras == null) {
- mExtras = new Bundle();
- }
- return mExtras;
+ return mUserExtras;
+ }
+
+ private Bundle getAllExtras() {
+ final Bundle saveExtras = (Bundle) mUserExtras.clone();
+ saveExtras.putAll(mN.extras);
+ return saveExtras;
}
/**
@@ -2918,6 +2861,21 @@
}
/**
+ * Alter the complete list of actions attached to this notification.
+ * @see #addAction(Action).
+ *
+ * @param actions
+ * @return
+ */
+ public Builder setActions(Action... actions) {
+ mActions.clear();
+ for (int i = 0; i < actions.length; i++) {
+ mActions.add(actions[i]);
+ }
+ return this;
+ }
+
+ /**
* Add a rich notification style to be applied at build time.
*
* @param style Object responsible for modifying the notification style.
@@ -2927,6 +2885,9 @@
mStyle = style;
if (mStyle != null) {
mStyle.setBuilder(this);
+ mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName());
+ } else {
+ mN.extras.remove(EXTRA_TEMPLATE);
}
}
return this;
@@ -2941,7 +2902,7 @@
* @return The same Builder.
*/
public Builder setVisibility(int visibility) {
- mVisibility = visibility;
+ mN.visibility = visibility;
return this;
}
@@ -2952,7 +2913,12 @@
* @return The same Builder.
*/
public Builder setPublicVersion(Notification n) {
- mPublicVersion = n;
+ if (n != null) {
+ mN.publicVersion = new Notification();
+ n.cloneInto(mN.publicVersion, /*heavy=*/ true);
+ } else {
+ mN.publicVersion = null;
+ }
return this;
}
@@ -2970,9 +2936,9 @@
*/
public void setFlag(int mask, boolean value) {
if (value) {
- mFlags |= mask;
+ mN.flags |= mask;
} else {
- mFlags &= ~mask;
+ mN.flags &= ~mask;
}
}
@@ -2984,7 +2950,7 @@
* @return The same Builder.
*/
public Builder setColor(@ColorInt int argb) {
- mColor = argb;
+ mN.color = argb;
return this;
}
@@ -3075,7 +3041,6 @@
contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
contentView.setViewVisibility(R.id.progress, View.GONE);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
- contentView.setViewVisibility(R.id.time, View.GONE);
}
private RemoteViews applyStandardTemplate(int resId) {
@@ -3093,39 +3058,43 @@
boolean showLine3 = false;
boolean showLine2 = false;
boolean contentTextInLine2 = false;
+ final Bundle ex = mN.extras;
- if (mLargeIcon != null) {
- contentView.setImageViewIcon(R.id.icon, mLargeIcon);
- processLargeLegacyIcon(mLargeIcon, contentView);
- contentView.setImageViewIcon(R.id.right_icon, mSmallIcon);
+ if (mN.mLargeIcon != null) {
+ contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
+ processLargeLegacyIcon(mN.mLargeIcon, contentView);
+ contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
- processSmallRightIcon(mSmallIcon, contentView);
+ processSmallRightIcon(mN.mSmallIcon, contentView);
} else { // small icon at left
- contentView.setImageViewIcon(R.id.icon, mSmallIcon);
+ contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
contentView.setViewVisibility(R.id.icon, View.VISIBLE);
- processSmallIconAsLarge(mSmallIcon, contentView);
+ processSmallIconAsLarge(mN.mSmallIcon, contentView);
}
- if (mContentTitle != null) {
- contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
+ if (ex.getCharSequence(EXTRA_TITLE) != null) {
+ contentView.setTextViewText(R.id.title,
+ processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
}
- if (mContentText != null) {
- contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
+ if (ex.getCharSequence(EXTRA_TEXT) != null) {
+ contentView.setTextViewText(R.id.text,
+ processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
showLine3 = true;
}
- if (mContentInfo != null) {
- contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
+ if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
+ contentView.setTextViewText(R.id.info,
+ processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
contentView.setViewVisibility(R.id.info, View.VISIBLE);
showLine3 = true;
- } else if (mNumber > 0) {
+ } else if (mN.number > 0) {
final int tooBig = mContext.getResources().getInteger(
R.integer.status_bar_notification_info_maxnum);
- if (mNumber > tooBig) {
+ if (mN.number > tooBig) {
contentView.setTextViewText(R.id.info, processLegacyText(
mContext.getResources().getString(
R.string.status_bar_notification_info_overflow)));
} else {
NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
+ contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
}
contentView.setViewVisibility(R.id.info, View.VISIBLE);
showLine3 = true;
@@ -3134,10 +3103,12 @@
}
// Need to show three lines?
- if (mSubText != null) {
- contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
- if (mContentText != null) {
- contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
+ if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
+ contentView.setTextViewText(R.id.text,
+ processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
+ if (ex.getCharSequence(EXTRA_TEXT) != null) {
+ contentView.setTextViewText(R.id.text2,
+ processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
contentView.setViewVisibility(R.id.text2, View.VISIBLE);
showLine2 = true;
contentTextInLine2 = true;
@@ -3146,15 +3117,18 @@
}
} else {
contentView.setViewVisibility(R.id.text2, View.GONE);
- if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) {
+ final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+ final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+ final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ if (hasProgress && (max != 0 || ind)) {
contentView.setViewVisibility(R.id.progress, View.VISIBLE);
contentView.setProgressBar(
- R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
+ R.id.progress, max, progress, ind);
contentView.setProgressBackgroundTintList(
R.id.progress, ColorStateList.valueOf(mContext.getColor(
R.color.notification_progress_background_color)));
- if (mColor != COLOR_DEFAULT) {
- ColorStateList colorStateList = ColorStateList.valueOf(mColor);
+ if (mN.color != COLOR_DEFAULT) {
+ ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
contentView.setProgressTintList(R.id.progress, colorStateList);
contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
}
@@ -3170,20 +3144,21 @@
}
if (showsTimeOrChronometer()) {
- if (mUseChronometer) {
+ if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
- mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+ mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
contentView.setBoolean(R.id.chronometer, "setStarted", true);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", mWhen);
+ contentView.setLong(R.id.time, "setTime", mN.when);
}
}
// Adjust padding depending on line count and font size.
- contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext,
- mHasThreeLines, mContext.getResources().getConfiguration().fontScale),
+ contentView.setViewPadding(R.id.line1, 0,
+ calculateTopPadding(mContext, hasThreeLines(),
+ mContext.getResources().getConfiguration().fontScale),
0, 0);
// We want to add badge to first line of text.
@@ -3196,7 +3171,8 @@
// Note getStandardView may hide line 3 again.
contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
- contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
+ contentView.setViewVisibility(R.id.overflow_divider,
+ showLine3 ? View.VISIBLE : View.GONE);
return contentView;
}
@@ -3205,7 +3181,7 @@
* otherwise
*/
private boolean showsTimeOrChronometer() {
- return mWhen != 0 && mShowWhen;
+ return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
}
/**
@@ -3216,15 +3192,19 @@
* is going to have one or two lines
*/
private boolean hasThreeLines() {
- boolean contentTextInLine2 = mSubText != null && mContentText != null;
+ final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
+ boolean contentTextInLine2 = subText != null && text != null;
boolean hasProgress = mStyle == null || mStyle.hasProgress();
// If we have content text in line 2, badge goes into line 2, or line 3 otherwise
boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
- boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0
- || badgeInLine3;
- boolean hasLine2 = (mSubText != null && mContentText != null) ||
- (hasProgress && mSubText == null
- && (mProgressMax != 0 || mProgressIndeterminate));
+ boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
+ || mN.number > 0 || badgeInLine3;
+ final Bundle ex = mN.extras;
+ final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+ final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ boolean hasLine2 = (subText != null && text != null) ||
+ (hasProgress && subText == null && (max != 0 || ind));
return hasLine2 && hasLine3;
}
@@ -3271,29 +3251,48 @@
return big;
}
- private RemoteViews makeContentView() {
- if (mContentView != null) {
- return mContentView;
- } else {
- return applyStandardTemplate(getBaseLayoutResource());
+ /**
+ * Construct a RemoteViews for the final 1U notification layout. In order:
+ * 1. Custom contentView from the caller
+ * 2. Style's proposed content view
+ * 3. Standard template view
+ */
+ public RemoteViews makeContentView() {
+ if (mN.contentView != null) {
+ return mN.contentView;
+ } else if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeContentView();
+ if (styleView != null) {
+ return styleView;
+ }
}
+ return applyStandardTemplate(getBaseLayoutResource());
}
- private RemoteViews makeTickerView() {
- if (mTickerView != null) {
- return mTickerView;
- }
- return null; // tickers are not created by default anymore
- }
-
- private RemoteViews makeBigContentView() {
- if (mActions.size() == 0) return null;
+ /**
+ * Construct a RemoteViews for the final big notification layout.
+ */
+ public RemoteViews makeBigContentView() {
+ if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeBigContentView();
+ if (styleView != null) {
+ return styleView;
+ }
+ } else if (mActions.size() == 0) return null;
return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
- private RemoteViews makeHeadsUpContentView() {
- if (mActions.size() == 0) return null;
+ /**
+ * Construct a RemoteViews for the final heads-up notification layout.
+ */
+ public RemoteViews makeHeadsUpContentView() {
+ if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+ if (styleView != null) {
+ return styleView;
+ }
+ } else if (mActions.size() == 0) return null;
return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
@@ -3320,11 +3319,11 @@
* doesn't create material notifications by itself) app.
*/
private boolean isLegacy() {
- return mColorUtil != null;
+ return getColorUtil() != null;
}
private void processLegacyAction(Action action, RemoteViews button) {
- if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) {
+ if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, action.getIcon())) {
button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
mContext.getColor(R.color.notification_action_color_filter),
PorterDuff.Mode.MULTIPLY);
@@ -3333,7 +3332,7 @@
private CharSequence processLegacyText(CharSequence charSequence) {
if (isLegacy()) {
- return mColorUtil.invertCharSequenceColors(charSequence);
+ return getColorUtil().invertCharSequenceColors(charSequence);
} else {
return charSequence;
}
@@ -3349,7 +3348,7 @@
PorterDuff.Mode.SRC_ATOP, -1);
applyLargeIconBackground(contentView);
} else {
- if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+ if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
applyLargeIconBackground(contentView);
}
}
@@ -3362,7 +3361,7 @@
// TODO: also check bounds, transparency, that sort of thing.
private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
if (largeIcon != null && isLegacy()
- && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) {
+ && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
applyLargeIconBackground(contentView);
} else {
removeLargeIconBackground(contentView);
@@ -3404,7 +3403,7 @@
}
final boolean gray = isLegacy()
&& smallIcon.getType() == Icon.TYPE_RESOURCE
- && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId());
+ && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
if (!isLegacy() || gray) {
contentView.setInt(R.id.right_icon,
"setBackgroundResource",
@@ -3421,17 +3420,17 @@
}
private int sanitizeColor() {
- if (mColor != COLOR_DEFAULT) {
- mColor |= 0xFF000000; // no alpha for custom colors
+ if (mN.color != COLOR_DEFAULT) {
+ mN.color |= 0xFF000000; // no alpha for custom colors
}
- return mColor;
+ return mN.color;
}
private int resolveColor() {
- if (mColor == COLOR_DEFAULT) {
+ if (mN.color == COLOR_DEFAULT) {
return mContext.getColor(R.color.notification_icon_bg_color);
}
- return mColor;
+ return mN.color;
}
/**
@@ -3439,165 +3438,25 @@
* @hide
*/
public Notification buildUnstyled() {
- Notification n = new Notification();
- n.when = mWhen;
- n.mSmallIcon = mSmallIcon;
- if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) {
- n.icon = mSmallIcon.getResId();
- }
- n.iconLevel = mSmallIconLevel;
- n.number = mNumber;
-
- n.color = sanitizeColor();
-
- setBuilderContentView(n, makeContentView());
- n.contentIntent = mContentIntent;
- n.deleteIntent = mDeleteIntent;
- n.fullScreenIntent = mFullScreenIntent;
- n.tickerText = mTickerText;
- n.tickerView = makeTickerView();
- n.mLargeIcon = mLargeIcon;
- if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) {
- n.largeIcon = mLargeIcon.getBitmap();
- }
- n.sound = mSound;
- n.audioStreamType = mAudioStreamType;
- n.audioAttributes = mAudioAttributes;
- n.vibrate = mVibrate;
- n.ledARGB = mLedArgb;
- n.ledOnMS = mLedOnMs;
- n.ledOffMS = mLedOffMs;
- n.defaults = mDefaults;
- n.flags = mFlags;
- setBuilderBigContentView(n, makeBigContentView());
- setBuilderHeadsUpContentView(n, makeHeadsUpContentView());
- if (mLedOnMs != 0 || mLedOffMs != 0) {
- n.flags |= FLAG_SHOW_LIGHTS;
- }
- if ((mDefaults & DEFAULT_LIGHTS) != 0) {
- n.flags |= FLAG_SHOW_LIGHTS;
- }
- n.category = mCategory;
- n.mGroupKey = mGroupKey;
- n.mSortKey = mSortKey;
- n.priority = mPriority;
if (mActions.size() > 0) {
- n.actions = new Action[mActions.size()];
- mActions.toArray(n.actions);
+ mN.actions = new Action[mActions.size()];
+ mActions.toArray(mN.actions);
}
- n.visibility = mVisibility;
-
- if (mPublicVersion != null) {
- n.publicVersion = new Notification();
- mPublicVersion.cloneInto(n.publicVersion, true);
+ if (!mPersonList.isEmpty()) {
+ mN.extras.putStringArray(EXTRA_PEOPLE,
+ mPersonList.toArray(new String[mPersonList.size()]));
}
if (mTopics.size() > 0) {
- n.topics = new Topic[mTopics.size()];
- mTopics.toArray(n.topics);
+ mN.topics = new Topic[mTopics.size()];
+ mTopics.toArray(mN.topics);
}
- // Note: If you're adding new fields, also update restoreFromNotitification().
- return n;
+ return mN;
}
- /**
- * Capture, in the provided bundle, semantic information used in the construction of
- * this Notification object.
- * @hide
- */
- public void populateExtras(Bundle extras) {
- // Store original information used in the construction of this object
- extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
- extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO,
- mContext.getApplicationInfo());
- extras.putCharSequence(EXTRA_TITLE, mContentTitle);
- extras.putCharSequence(EXTRA_TEXT, mContentText);
- extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
- extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
- extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon);
- extras.putInt(EXTRA_PROGRESS, mProgress);
- extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
- extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
- extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
- extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
- if (mLargeIcon != null) {
- extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
- }
- if (!mPeople.isEmpty()) {
- extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
- }
- // NOTE: If you're adding new extras also update restoreFromNotification().
- }
-
-
- /**
- * @hide
- */
- public static void stripForDelivery(Notification n) {
- if (!STRIP_AND_REBUILD) {
- return;
- }
-
- String templateClass = n.extras.getString(EXTRA_TEMPLATE);
- // Only strip views for known Styles because we won't know how to
- // re-create them otherwise.
- boolean stripViews = TextUtils.isEmpty(templateClass) ||
- getNotificationStyleClass(templateClass) != null;
-
- boolean isStripped = false;
-
- if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) {
- // TODO: Would like to check for equality here, but if the notification
- // has been cloned, we can't.
- n.largeIcon = null;
- n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true);
- isStripped = true;
- }
- // Get rid of unmodified BuilderRemoteViews.
-
- if (stripViews &&
- n.contentView instanceof BuilderRemoteViews &&
- n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.contentView.getSequenceNumber()) {
- n.contentView = null;
- n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true);
- n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
- isStripped = true;
- }
- if (stripViews &&
- n.bigContentView instanceof BuilderRemoteViews &&
- n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.bigContentView.getSequenceNumber()) {
- n.bigContentView = null;
- n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true);
- n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
- isStripped = true;
- }
- if (stripViews &&
- n.headsUpContentView instanceof BuilderRemoteViews &&
- n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.headsUpContentView.getSequenceNumber()) {
- n.headsUpContentView = null;
- n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true);
- n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
- isStripped = true;
- }
-
- if (isStripped) {
- n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true);
- }
- }
-
- /**
- * @hide
- */
- public static Notification rebuild(Context context, Notification n) {
- Bundle extras = n.extras;
- if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n;
- extras.remove(EXTRA_NEEDS_REBUILD);
-
+ public static Notification.Builder recoverBuilder(Context context, Notification n) {
// Re-create notification context so we can access app resources.
- ApplicationInfo applicationInfo = extras.getParcelable(
- EXTRA_REBUILD_CONTEXT_APPLICATION_INFO);
+ ApplicationInfo applicationInfo = n.extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO);
Context builderContext;
try {
builderContext = context.createApplicationContext(applicationInfo,
@@ -3607,58 +3466,7 @@
builderContext = context; // try with our context
}
- Builder b = new Builder(builderContext, n);
- return b.rebuild();
- }
-
- /**
- * Rebuilds the notification passed in to the rebuild-constructor
- * {@link #Builder(Context, Notification)}.
- *
- * <p>
- * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode.
- *
- * @hide
- */
- private Notification rebuild() {
- if (mRebuildNotification == null) {
- throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
- }
- mHasThreeLines = hasThreeLines();
-
- Bundle extras = mRebuildNotification.extras;
-
- if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) {
- mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
- }
- extras.remove(EXTRA_REBUILD_LARGE_ICON);
-
- if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) {
- setBuilderContentView(mRebuildNotification, makeContentView());
- if (mStyle != null) {
- mStyle.populateContentView(mRebuildNotification);
- }
- }
- extras.remove(EXTRA_REBUILD_CONTENT_VIEW);
-
- if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) {
- setBuilderBigContentView(mRebuildNotification, makeBigContentView());
- if (mStyle != null) {
- mStyle.populateBigContentView(mRebuildNotification);
- }
- }
- extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW);
-
- if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) {
- setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView());
- if (mStyle != null) {
- mStyle.populateHeadsUpContentView(mRebuildNotification);
- }
- }
- extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
-
- mHasThreeLines = false;
- return mRebuildNotification;
+ return new Builder(builderContext, n);
}
private static Class<? extends Style> getNotificationStyleClass(String templateClass) {
@@ -3674,91 +3482,15 @@
private void setBuilderContentView(Notification n, RemoteViews contentView) {
n.contentView = contentView;
- if (contentView instanceof BuilderRemoteViews) {
- mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
- contentView.getSequenceNumber());
- }
}
private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
n.bigContentView = bigContentView;
- if (bigContentView instanceof BuilderRemoteViews) {
- mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
- bigContentView.getSequenceNumber());
- }
}
private void setBuilderHeadsUpContentView(Notification n,
RemoteViews headsUpContentView) {
n.headsUpContentView = headsUpContentView;
- if (headsUpContentView instanceof BuilderRemoteViews) {
- mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
- headsUpContentView.getSequenceNumber());
- }
- }
-
- private void restoreFromNotification(Notification n) {
-
- // Notification fields.
- mWhen = n.when;
- mSmallIcon = n.mSmallIcon;
- mSmallIconLevel = n.iconLevel;
- mNumber = n.number;
-
- mColor = n.color;
-
- mContentView = n.contentView;
- mDeleteIntent = n.deleteIntent;
- mFullScreenIntent = n.fullScreenIntent;
- mTickerText = n.tickerText;
- mTickerView = n.tickerView;
- mLargeIcon = n.mLargeIcon;
- mSound = n.sound;
- mAudioStreamType = n.audioStreamType;
- mAudioAttributes = n.audioAttributes;
-
- mVibrate = n.vibrate;
- mLedArgb = n.ledARGB;
- mLedOnMs = n.ledOnMS;
- mLedOffMs = n.ledOffMS;
- mDefaults = n.defaults;
- mFlags = n.flags;
-
- mCategory = n.category;
- mGroupKey = n.mGroupKey;
- mSortKey = n.mSortKey;
- mPriority = n.priority;
- mActions.clear();
- if (n.actions != null) {
- Collections.addAll(mActions, n.actions);
- }
- mVisibility = n.visibility;
-
- mPublicVersion = n.publicVersion;
-
- if (n.topics != null) {
- Collections.addAll(mTopics, n.topics);
- }
-
- // Extras.
- Bundle extras = n.extras;
- mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
- mContentTitle = extras.getCharSequence(EXTRA_TITLE);
- mContentText = extras.getCharSequence(EXTRA_TEXT);
- mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
- mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT);
- mProgress = extras.getInt(EXTRA_PROGRESS);
- mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX);
- mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
- mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER);
- mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN);
- if (extras.containsKey(EXTRA_LARGE_ICON)) {
- mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON);
- }
- if (extras.containsKey(EXTRA_PEOPLE)) {
- mPeople.clear();
- Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
- }
}
/**
@@ -3774,38 +3506,23 @@
* object.
*/
public Notification build() {
- if (mSmallIcon != null) {
- mSmallIcon.convertToAshmem();
+ // first, add any extras from the calling code
+ if (mUserExtras != null) {
+ mN.extras = getAllExtras();
}
- if (mLargeIcon != null) {
- mLargeIcon.convertToAshmem();
- }
+
+ // lazy stuff from mContext; see comment in Builder(Context, Notification)
+ mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo());
mOriginatingUserId = mContext.getUserId();
- mHasThreeLines = hasThreeLines();
+ mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
- Notification n = buildUnstyled();
+ buildUnstyled();
if (mStyle != null) {
- mStyle.purgeResources();
- n = mStyle.buildStyled(n);
+ mStyle.buildStyled(mN);
}
- if (mExtras != null) {
- n.extras.putAll(mExtras);
- }
-
- if (mRebuildBundle.size() > 0) {
- n.extras.putAll(mRebuildBundle);
- mRebuildBundle.clear();
- }
-
- populateExtras(n.extras);
- if (mStyle != null) {
- mStyle.addExtras(n.extras);
- }
-
- mHasThreeLines = false;
- return n;
+ return mN;
}
/**
@@ -3901,14 +3618,15 @@
checkBuilder();
// Nasty.
- CharSequence oldBuilderContentTitle = mBuilder.mContentTitle;
+ CharSequence oldBuilderContentTitle =
+ mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE);
if (mBigContentTitle != null) {
mBuilder.setContentTitle(mBigContentTitle);
}
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
- mBuilder.mContentTitle = oldBuilderContentTitle;
+ mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle);
if (mBigContentTitle != null && mBigContentTitle.equals("")) {
contentView.setViewVisibility(R.id.line1, View.GONE);
@@ -3919,7 +3637,7 @@
// The last line defaults to the subtext, but can be replaced by mSummaryText
final CharSequence overflowText =
mSummaryTextSet ? mSummaryText
- : mBuilder.mSubText;
+ : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
if (overflowText != null) {
contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
@@ -3935,6 +3653,31 @@
}
/**
+ * Construct a Style-specific RemoteViews for the final 1U notification layout.
+ * The default implementation has nothing additional to add.
+ * @hide
+ */
+ public RemoteViews makeContentView() {
+ return null;
+ }
+
+ /**
+ * Construct a Style-specific RemoteViews for the final big notification layout.
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
+ return null;
+ }
+
+ /**
+ * Construct a Style-specific RemoteViews for the final HUN layout.
+ * @hide
+ */
+ public RemoteViews makeHeadsUpContentView() {
+ return null;
+ }
+
+ /**
* Changes the padding of the first line such that the big and small content view have the
* same top padding.
*
@@ -3942,12 +3685,13 @@
*/
protected void applyTopPadding(RemoteViews contentView) {
int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
- mBuilder.mHasThreeLines,
+ mBuilder.hasThreeLines(),
mBuilder.mContext.getResources().getConfiguration().fontScale);
contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
}
/**
+ * Apply any style-specific extras to this notification before shipping it out.
* @hide
*/
public void addExtras(Bundle extras) {
@@ -3961,6 +3705,7 @@
}
/**
+ * Reconstruct the internal state of this Style object from extras.
* @hide
*/
protected void restoreFromExtras(Bundle extras) {
@@ -3978,10 +3723,7 @@
* @hide
*/
public Notification buildStyled(Notification wip) {
- populateTickerView(wip);
- populateContentView(wip);
- populateBigContentView(wip);
- populateHeadsUpContentView(wip);
+ addExtras(wip.extras);
return wip;
}
@@ -3990,26 +3732,6 @@
*/
public void purgeResources() {}
- // The following methods are split out so we can re-create notification partially.
- /**
- * @hide
- */
- protected void populateTickerView(Notification wip) {}
- /**
- * @hide
- */
- protected void populateContentView(Notification wip) {}
-
- /**
- * @hide
- */
- protected void populateBigContentView(Notification wip) {}
-
- /**
- * @hide
- */
- protected void populateHeadsUpContentView(Notification wip) {}
-
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
@@ -4115,29 +3837,33 @@
}
}
- private RemoteViews makeBigContentView() {
- // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet
+ /**
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
+ // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
// This covers the following cases:
// 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
- // mLargeIcon
- // 2. !mBigLargeIconSet -> mLargeIcon applies
+ // mN.mLargeIcon
+ // 2. !mBigLargeIconSet -> mN.mLargeIcon applies
Icon oldLargeIcon = null;
if (mBigLargeIconSet) {
- oldLargeIcon = mBuilder.mLargeIcon;
- mBuilder.mLargeIcon = mBigLargeIcon;
+ oldLargeIcon = mBuilder.mN.mLargeIcon;
+ mBuilder.mN.mLargeIcon = mBigLargeIcon;
}
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
if (mBigLargeIconSet) {
- mBuilder.mLargeIcon = oldLargeIcon;
+ mBuilder.mN.mLargeIcon = oldLargeIcon;
}
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
applyTopPadding(contentView);
- boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null;
+ boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
+ && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
mBuilder.addProfileBadge(contentView,
twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
return contentView;
@@ -4168,14 +3894,6 @@
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
-
- /**
- * @hide
- */
- @Override
- public void populateBigContentView(Notification wip) {
- mBuilder.setBuilderBigContentView(wip, makeBigContentView());
- }
}
/**
@@ -4255,15 +3973,19 @@
mBigText = extras.getCharSequence(EXTRA_BIG_TEXT);
}
- private RemoteViews makeBigContentView() {
+ /**
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
// Nasty
- CharSequence oldBuilderContentText = mBuilder.mContentText;
- mBuilder.mContentText = null;
+ CharSequence oldBuilderContentText =
+ mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT);
+ mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
- mBuilder.mContentText = oldBuilderContentText;
+ mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
@@ -4282,7 +4004,8 @@
private int calculateMaxLines() {
int lineCount = MAX_LINES;
boolean hasActions = mBuilder.mActions.size() > 0;
- boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null;
+ boolean hasSummary = (mSummaryTextSet ? mSummaryText
+ : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT)) != null;
if (hasActions) {
lineCount -= LINES_CONSUMED_BY_ACTIONS;
}
@@ -4291,19 +4014,11 @@
}
// If we have less top padding at the top, we can fit less lines.
- if (!mBuilder.mHasThreeLines) {
+ if (!mBuilder.hasThreeLines()) {
lineCount--;
}
return lineCount;
}
-
- /**
- * @hide
- */
- @Override
- public void populateBigContentView(Notification wip) {
- mBuilder.setBuilderBigContentView(wip, makeBigContentView());
- }
}
/**
@@ -4384,16 +4099,18 @@
}
}
- private RemoteViews makeBigContentView() {
+ /**
+ * @hide
+ */
+ public RemoteViews makeBigContentView() {
// Remove the content text so line3 disappears unless you have a summary
-
// Nasty
- CharSequence oldBuilderContentText = mBuilder.mContentText;
- mBuilder.mContentText = null;
+ CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
+ mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
- mBuilder.mContentText = oldBuilderContentText;
+ mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
contentView.setViewVisibility(R.id.text2, View.GONE);
@@ -4437,14 +4154,6 @@
return contentView;
}
-
- /**
- * @hide
- */
- @Override
- public void populateBigContentView(Notification wip) {
- mBuilder.setBuilderBigContentView(wip, makeBigContentView());
- }
}
/**
@@ -4536,16 +4245,16 @@
* @hide
*/
@Override
- public void populateContentView(Notification wip) {
- mBuilder.setBuilderContentView(wip, makeMediaContentView());
+ public RemoteViews makeContentView() {
+ return makeMediaContentView();
}
/**
* @hide
*/
@Override
- public void populateBigContentView(Notification wip) {
- mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView());
+ public RemoteViews makeBigContentView() {
+ return makeMediaBigContentView();
}
/** @hide */
@@ -4659,7 +4368,7 @@
R.color.notification_media_secondary_color);
contentView.setTextColor(R.id.title, primaryColor);
if (mBuilder.showsTimeOrChronometer()) {
- if (mBuilder.mUseChronometer) {
+ if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setTextColor(R.id.chronometer, secondaryColor);
} else {
contentView.setTextColor(R.id.time, secondaryColor);
@@ -5503,7 +5212,7 @@
/**
* Gets the accent color.
*
- * @see setColor
+ * @see #setColor
*/
@ColorInt
public int getColor() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cb0ff33..07b4d39 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -207,33 +207,7 @@
*/
public void notify(String tag, int id, Notification notification)
{
- int[] idOut = new int[1];
- INotificationManager service = getService();
- String pkg = mContext.getPackageName();
- if (notification.sound != null) {
- notification.sound = notification.sound.getCanonicalUri();
- if (StrictMode.vmFileUriExposureEnabled()) {
- notification.sound.checkFileUriExposed("Notification.sound");
- }
- }
- fixLegacySmallIcon(notification, pkg);
- if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
- if (notification.getSmallIcon() == null) {
- throw new IllegalArgumentException("Invalid notification (no valid small icon): "
- + notification);
- }
- }
- if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
- Notification stripped = notification.clone();
- Builder.stripForDelivery(stripped);
- try {
- service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
- stripped, idOut, UserHandle.myUserId());
- if (id != idOut[0]) {
- Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
- }
- } catch (RemoteException e) {
- }
+ notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
/**
@@ -251,12 +225,17 @@
}
}
fixLegacySmallIcon(notification, pkg);
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ if (notification.getSmallIcon() == null) {
+ throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ + notification);
+ }
+ }
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
- Notification stripped = notification.clone();
- Builder.stripForDelivery(stripped);
+ final Notification copy = notification.clone();
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
- stripped, idOut, user.getIdentifier());
+ copy, idOut, user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
@@ -287,13 +266,7 @@
*/
public void cancel(String tag, int id)
{
- INotificationManager service = getService();
- String pkg = mContext.getPackageName();
- if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
- try {
- service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId());
- } catch (RemoteException e) {
- }
+ cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId()));
}
/**
@@ -375,29 +348,6 @@
/**
* @hide
*/
- public boolean setZenModeConfig(ZenModeConfig config, String reason) {
- INotificationManager service = getService();
- try {
- return service.setZenModeConfig(config, reason);
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * @hide
- */
- public void requestZenModeConditions(IConditionListener listener, int relevance) {
- INotificationManager service = getService();
- try {
- service.requestZenModeConditions(listener, relevance);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * @hide
- */
public int getZenMode() {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0a0d77d..4270e16 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,8 @@
package android.app.admin;
+import android.os.Bundle;
+
import java.util.List;
/**
@@ -69,4 +71,13 @@
* @return true if the uid is an active admin with the given policy.
*/
public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
+
+ /**
+ * Takes a {@link Bundle} containing "base" user restrictions stored in
+ * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
+ * and the profile owner and returns the merged restrictions.
+ *
+ * This method always returns a new {@link Bundle}.
+ */
+ public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
}
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index cddf47a..39902d7 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -14,6 +14,7 @@
*/
public class AssistContent implements Parcelable {
private boolean mIsAppProvidedIntent = false;
+ private boolean mIsAppProvidedWebUri = false;
private Intent mIntent;
private String mStructuredData;
private ClipData mClipData;
@@ -39,7 +40,7 @@
Uri uri = intent.getData();
if (uri != null) {
if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
- setWebUri(uri);
+ mUri = uri;
}
}
}
@@ -116,6 +117,7 @@
* leave the null and only report the local intent and clip data.
*/
public void setWebUri(Uri uri) {
+ mIsAppProvidedWebUri = true;
mUri = uri;
}
@@ -128,6 +130,16 @@
}
/**
+ * Returns whether or not the current {@link #getWebUri} was explicitly provided in
+ * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. If not,
+ * the Intent was automatically set based on
+ * {@link android.app.Activity#getIntent Activity.getIntent}.
+ */
+ public boolean isAppProvidedWebUri() {
+ return mIsAppProvidedWebUri;
+ }
+
+ /**
* Return Bundle for extra vendor-specific data that can be modified and examined.
*/
public Bundle getExtras() {
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index ff4ebee..874026f 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -1059,6 +1059,41 @@
}
/**
+ * Sets whether audio routing is allowed.
+ *
+ * Note: This is an internal function and shouldn't be exposed
+ */
+ public void setAudioRouteAllowed(boolean allowed) {
+ if (VDBG) log("setAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ mService.setAudioRouteAllowed(allowed);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
+ * Returns whether audio routing is allowed.
+ *
+ * Note: This is an internal function and shouldn't be exposed
+ */
+ public boolean getAudioRouteAllowed() {
+ if (VDBG) log("getAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getAudioRouteAllowed();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
* Initiates a connection of audio channel.
*
* It setup SCO channel with remote connected Handsfree AG device.
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
index e518b7d..79ae4e4 100644
--- a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -62,6 +62,8 @@
int getAudioState(in BluetoothDevice device);
boolean connectAudio();
boolean disconnectAudio();
+ void setAudioRouteAllowed(boolean allowed);
+ boolean getAudioRouteAllowed();
Bundle getCurrentAgFeatures(in BluetoothDevice device);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6740d43..d7e94d0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1658,6 +1658,14 @@
= "android.intent.extra.GET_PERMISSIONS_APP_LABEL_LIST_RESULT";
/**
+ * Boolean list describing if the app is a system app for apps that have one or more runtime
+ * permissions.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT";
+
+ /**
* Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_COUNT} broadcasts.
* @hide
*/
diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java
index 56b3173..a5a8e3f 100644
--- a/core/java/android/content/pm/AppsQueryHelper.java
+++ b/core/java/android/content/pm/AppsQueryHelper.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -46,6 +48,14 @@
*/
public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1;
+ /**
+ * Return all input methods that are marked as default.
+ * <p>When this flag is set, {@code user} specified in
+ * {@link #queryApps(int, boolean, UserHandle)} must be
+ * {@link UserHandle#myUserId user of the current process}.
+ */
+ public static int GET_DEFAULT_IMES = 1 << 2;
+
private final Context mContext;
private List<ApplicationInfo> mAllApps;
@@ -56,13 +66,14 @@
/**
* Return a List of all packages that satisfy a specified criteria.
* @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS},
- * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM}
+ * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}.
* @param systemAppsOnly if true, only system apps will be returned
* @param user user, whose apps are queried
*/
public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) {
boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0;
boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0;
+ boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0;
if (mAllApps == null) {
mAllApps = getAllApps(user.getIdentifier());
}
@@ -118,6 +129,33 @@
}
}
+ if (defaultImes) {
+ if (UserHandle.myUserId() != user.getIdentifier()) {
+ throw new IllegalArgumentException("Specified user handle " + user
+ + " is not a user of the current process.");
+ }
+ List<InputMethodInfo> imis = getInputMethodList();
+ int imisSize = imis.size();
+ ArraySet<String> defaultImePackages = new ArraySet<>();
+ for (int i = 0; i < imisSize; i++) {
+ InputMethodInfo imi = imis.get(i);
+ if (imi.isDefault(mContext)) {
+ defaultImePackages.add(imi.getPackageName());
+ }
+ }
+ final int allAppsSize = mAllApps.size();
+ for (int i = 0; i < allAppsSize; i++) {
+ final ApplicationInfo appInfo = mAllApps.get(i);
+ if (systemAppsOnly && !appInfo.isSystemApp()) {
+ continue;
+ }
+ final String packageName = appInfo.packageName;
+ if (defaultImePackages.contains(packageName)) {
+ result.add(packageName);
+ }
+ }
+ }
+
return result;
}
@@ -150,4 +188,12 @@
throw new IllegalStateException("Package manager has died", e);
}
}
+
+ @VisibleForTesting
+ @SuppressWarnings("unchecked")
+ protected List<InputMethodInfo> getInputMethodList() {
+ InputMethodManager imm = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ return imm.getInputMethodList();
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aed1a0b..27ecf9f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1708,6 +1708,15 @@
public static final String FEATURE_BACKUP = "android.software.backup";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports freeform window management.
+ * Windows have title bars and can be moved and resized.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT
+ = "android.software.freeform_window_management";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports creating secondary users and managed profiles via
* {@link DevicePolicyManager}.
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 1fcfaca..51796eb 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -513,7 +513,7 @@
* {@link CameraManager#registerAvailabilityCallback} to be notified of such availability
* changes.</p>
*
- * @see registerAvailabilityCallback
+ * @see #registerAvailabilityCallback
*/
public static abstract class AvailabilityCallback {
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 6bfa2a4..1bb0fbb 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -119,8 +119,12 @@
//
// For one such example of this, see b/18867306.
//
- // TODO: Remove this special case altogether.
- if (before.isIPv4Provisioned() && !after.isIPv4Provisioned()) {
+ // Additionally, losing IPv6 provisioning can result in TCP
+ // connections getting stuck until timeouts fire and other
+ // baffling failures. Therefore, loss of either IPv4 or IPv6 on a
+ // previously dualstack network is deemed a lost of provisioning.
+ if ((before.isIPv4Provisioned() && !after.isIPv4Provisioned()) ||
+ (before.isIPv6Provisioned() && !after.isIPv6Provisioned())) {
return ProvisioningChange.LOST_PROVISIONING;
}
return ProvisioningChange.STILL_PROVISIONED;
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3ade170..12cac85 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -55,14 +55,12 @@
int getUserHandle(int userSerialNumber);
Bundle getUserRestrictions(int userHandle);
boolean hasUserRestriction(in String restrictionKey, int userHandle);
- void setUserRestrictions(in Bundle restrictions, int userHandle);
void setUserRestriction(String key, boolean value, int userId);
void setSystemControlledUserRestriction(String key, boolean value, int userId);
void setApplicationRestrictions(in String packageName, in Bundle restrictions,
int userHandle);
Bundle getApplicationRestrictions(in String packageName);
Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle);
- void removeRestrictions();
void setDefaultGuestRestrictions(in Bundle restrictions);
Bundle getDefaultGuestRestrictions();
boolean markGuestForDeletion(int userHandle);
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d64273a..73c2c80 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -44,7 +44,7 @@
private FastPrintWriter mOutPrintWriter;
private FastPrintWriter mErrPrintWriter;
- public void exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ResultReceiver resultReceiver) {
mTarget = target;
mIn = in;
@@ -89,6 +89,7 @@
mResultReceiver.send(res, null);
}
if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
+ return res;
}
public PrintWriter getOutPrintWriter() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1c1575e..2e31ab6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -21,6 +21,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
@@ -55,7 +56,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
@@ -67,7 +69,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
@@ -78,7 +81,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
@@ -89,7 +93,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -102,7 +107,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
@@ -114,7 +120,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
@@ -127,7 +134,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
@@ -139,7 +147,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -150,7 +159,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -163,7 +173,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_REMOVE_USER = "no_remove_user";
@@ -174,7 +185,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
@@ -187,7 +199,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -199,7 +212,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
@@ -213,7 +227,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
@@ -227,7 +242,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_FACTORY_RESET = "no_factory_reset";
@@ -241,7 +257,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_ADD_USER = "no_add_user";
@@ -252,7 +269,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
@@ -266,7 +284,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
@@ -280,7 +299,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
@@ -300,7 +320,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
@@ -312,7 +333,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
@@ -324,7 +346,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -336,7 +359,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
@@ -350,7 +374,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -361,7 +386,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_SMS = "no_sms";
@@ -373,7 +399,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_FUN = "no_fun";
@@ -393,7 +420,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
@@ -406,7 +434,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
@@ -417,7 +446,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
@@ -426,7 +456,8 @@
* Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction
* is always set for managed profiles.
* @hide
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_WALLPAPER = "no_wallpaper";
@@ -438,7 +469,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
@@ -447,7 +479,8 @@
* Specifies if a user is not allowed to record audio. This restriction is always enabled for
* background users. The default value is <code>false</code>.
*
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
* @hide
*/
@@ -466,7 +499,8 @@
*
* <p/>Key for user restrictions.
* <p/>Type: Boolean
- * @see #setUserRestrictions(Bundle)
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
public static final String ALLOW_PARENT_PROFILE_APP_LINKING
@@ -499,14 +533,9 @@
/** @hide */
public static final int PIN_VERIFICATION_SUCCESS = -1;
- private static UserManager sInstance = null;
-
/** @hide */
- public synchronized static UserManager get(Context context) {
- if (sInstance == null) {
- sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
- }
- return sInstance;
+ public static UserManager get(Context context) {
+ return (UserManager) context.getSystemService(Context.USER_SERVICE);
}
/** @hide */
@@ -740,36 +769,23 @@
}
/**
- * Sets all the user-wide restrictions for this user.
- * Requires the MANAGE_USERS permission.
- * @param restrictions the Bundle containing all the restrictions.
- * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
- * android.content.ComponentName, String)} or
- * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
- * android.content.ComponentName, String)} instead.
+ * This will no longer work. Device owners and profile owners should use
+ * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
*/
+ // System apps should use UserManager.setUserRestriction() instead.
@Deprecated
public void setUserRestrictions(Bundle restrictions) {
- setUserRestrictions(restrictions, Process.myUserHandle());
+ throw new UnsupportedOperationException("This method is no longer supported");
}
/**
- * Sets all the user-wide restrictions for the specified user.
- * Requires the MANAGE_USERS permission.
- * @param restrictions the Bundle containing all the restrictions.
- * @param userHandle the UserHandle of the user for whom to set the restrictions.
- * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
- * android.content.ComponentName, String)} or
- * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
- * android.content.ComponentName, String)} instead.
+ * This will no longer work. Device owners and profile owners should use
+ * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
*/
+ // System apps should use UserManager.setUserRestriction() instead.
@Deprecated
public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
- try {
- mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
- } catch (RemoteException re) {
- Log.w(TAG, "Could not set user restrictions", re);
- }
+ throw new UnsupportedOperationException("This method is no longer supported");
}
/**
@@ -784,9 +800,7 @@
*/
@Deprecated
public void setUserRestriction(String key, boolean value) {
- Bundle bundle = getUserRestrictions();
- bundle.putBoolean(key, value);
- setUserRestrictions(bundle);
+ setUserRestriction(key, value, Process.myUserHandle());
}
/**
@@ -882,9 +896,8 @@
try {
user = mService.createUser(name, flags);
if (user != null && !user.isAdmin()) {
- Bundle userRestrictions = mService.getUserRestrictions(user.id);
- addDefaultUserRestrictions(userRestrictions);
- mService.setUserRestrictions(userRestrictions, user.id);
+ mService.setUserRestriction(DISALLOW_SMS, true, user.id);
+ mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
}
} catch (RemoteException re) {
Log.w(TAG, "Could not create a user", re);
@@ -899,27 +912,22 @@
* @hide
*/
public UserInfo createGuest(Context context, String name) {
- UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);
- if (guest != null) {
- Settings.Secure.putStringForUser(context.getContentResolver(),
- Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
- try {
- Bundle guestRestrictions = mService.getDefaultGuestRestrictions();
- guestRestrictions.putBoolean(DISALLOW_SMS, true);
- guestRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- mService.setUserRestrictions(guestRestrictions, guest.id);
- } catch (RemoteException re) {
- Log.w(TAG, "Could not update guest restrictions");
+ UserInfo guest = null;
+ try {
+ guest = mService.createUser(name, UserInfo.FLAG_GUEST);
+ if (guest != null) {
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+ mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
+ mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
}
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not create a user", re);
}
return guest;
}
- private static void addDefaultUserRestrictions(Bundle restrictions) {
- restrictions.putBoolean(DISALLOW_OUTGOING_CALLS, true);
- restrictions.putBoolean(DISALLOW_SMS, true);
- }
-
/**
* Creates a user with the specified name and options as a profile of another user.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
@@ -1465,15 +1473,6 @@
return false;
}
- /** @hide */
- public void removeRestrictions() {
- try {
- mService.removeRestrictions();
- } catch (RemoteException re) {
- Log.w(TAG, "Could not change restrictions pin");
- }
- }
-
/**
* @hide
* Set restrictions that should apply to any future guest user that's created.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
new file mode 100644
index 0000000..d7be6d8
--- /dev/null
+++ b/core/java/android/os/UserManagerInternal.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class UserManagerInternal {
+ /**
+ * Lock that must be held when calling certain methods in this class.
+ *
+ * This is used to avoid dead lock between
+ * {@link com.android.server.pm.UserManagerService} and
+ * {@link com.android.server.devicepolicy.DevicePolicyManagerService}. This lock should not
+ * be newly taken while holding the DPMS lock, which would cause a dead lock. Take this
+ * lock first before taking the DPMS lock to avoid that.
+ */
+ public abstract Object getUserRestrictionsLock();
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+ * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+ *
+ * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+ */
+ public abstract void updateEffectiveUserRestrictionsRL(int userId);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
+ * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
+ *
+ * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
+ */
+ public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+
+ /**
+ * Returns the "base" user restrictions.
+ *
+ * Used by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+ * from MNC.
+ */
+ public abstract Bundle getBaseUserRestrictions(int userId);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+ * from MNC.
+ */
+ public abstract void setBaseUserRestrictionsByDpmsForMigration(int userId,
+ Bundle baseRestrictions);
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 11effd0..ad46c3d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5604,11 +5604,6 @@
public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
"enabled_notification_policy_access_packages";
- /**
- * @hide
- */
- public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
-
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
@@ -6277,6 +6272,15 @@
*/
public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external";
+ /**
+ * Whether any activity can be resized. When this is true, any
+ * activity, regardless of manifest values, can be resized for multi-window.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES
+ = "force_resizable_activities";
+
/**
* Whether user has enabled development settings.
*/
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index c679eda..bbac023 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -35,7 +35,8 @@
* the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
* able to create and update conditions for this service to monitor, include the
- * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
+ * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags and request the
+ * {@link android.Manifest.permission#ACCESS_NOTIFICATION_POLICY} permission. For example:</p>
* <pre>
* <service android:name=".MyConditionProvider"
* android:label="@string/service_name"
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index d424546..7e7b5fc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -31,6 +31,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.Bitmap;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
@@ -472,9 +473,10 @@
StatusBarNotification sbn = list.get(i);
Notification notification = sbn.getNotification();
try {
- Builder.rebuild(getContext(), notification);
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(notification);
+ // populate remote views for older clients.
+ maybePopulateRemoteViews(notification);
} catch (IllegalArgumentException e) {
if (corruptNotifications == null) {
corruptNotifications = new ArrayList<>(N);
@@ -676,6 +678,18 @@
}
}
+ /**
+ * Populates remote views for pre-N targeting apps.
+ */
+ private void maybePopulateRemoteViews(Notification notification) {
+ if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ Builder builder = Builder.recoverBuilder(getContext(), notification);
+ notification.contentView = builder.makeContentView();
+ notification.bigContentView = builder.makeBigContentView();
+ notification.headsUpContentView = builder.makeHeadsUpContentView();
+ }
+ }
+
private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
@@ -689,9 +703,10 @@
}
try {
- Notification.Builder.rebuild(getContext(), sbn.getNotification());
+ Notification notification = sbn.getNotification();
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(sbn.getNotification());
+ maybePopulateRemoteViews(sbn.getNotification());
} catch (IllegalArgumentException e) {
// drop corrupt notification
sbn = null;
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index de509b2..2459cfa 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -238,6 +238,7 @@
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
+ boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer);
boolean handled = Touch.onTouchEvent(widget, buffer, event);
if (widget.didTouchFocusSelect() && !isMouse) {
@@ -267,9 +268,9 @@
// Cursor can be active at any location in the text while mouse pointer can start
// selection from a totally different location. Use LAST_TAP_DOWN span to ensure
// text selection will start from mouse pointer location.
+ final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
if (isMouse && Touch.isSelectionStarted(buffer)) {
- int offset = buffer.getSpanStart(LAST_TAP_DOWN);
- Selection.setSelection(buffer, offset);
+ Selection.setSelection(buffer, startOffset);
}
if (isTouchSelecting(isMouse, buffer) && handled) {
@@ -284,9 +285,9 @@
// Update selection as we're moving the selection area.
// Get the current touch position
- int offset = widget.getOffsetForPosition(event.getX(), event.getY());
-
- Selection.extendSelection(buffer, offset);
+ final int offset = widget.getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection(buffer, Math.min(startOffset, offset),
+ Math.max(startOffset, offset));
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
@@ -300,10 +301,12 @@
return true;
}
- int offset = widget.getOffsetForPosition(event.getX(), event.getY());
- if (isTouchSelecting(isMouse, buffer)) {
+ if (wasTouchSelecting) {
+ final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
+ final int endOffset = widget.getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection(buffer, Math.min(startOffset, endOffset),
+ Math.max(startOffset, endOffset));
buffer.removeSpan(LAST_TAP_DOWN);
- Selection.extendSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index e5c729d..3b832dd 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -19,6 +19,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.util.Arrays;
+
import libcore.util.EmptyArray;
/**
@@ -239,6 +241,18 @@
}
/**
+ * Provides a copy of keys.
+ *
+ * @hide
+ * */
+ public int[] copyKeys() {
+ if (size() == 0) {
+ return null;
+ }
+ return Arrays.copyOf(mKeys, size());
+ }
+
+ /**
* {@inheritDoc}
*
* <p>This implementation composes a string by iterating over its mappings.
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index c1eb80d..37d6757 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -343,15 +343,15 @@
int seconds = (int) Math.floor(duration / 1000);
int days = 0, hours = 0, minutes = 0;
- if (seconds > SECONDS_PER_DAY) {
+ if (seconds >= SECONDS_PER_DAY) {
days = seconds / SECONDS_PER_DAY;
seconds -= days * SECONDS_PER_DAY;
}
- if (seconds > SECONDS_PER_HOUR) {
+ if (seconds >= SECONDS_PER_HOUR) {
hours = seconds / SECONDS_PER_HOUR;
seconds -= hours * SECONDS_PER_HOUR;
}
- if (seconds > SECONDS_PER_MINUTE) {
+ if (seconds >= SECONDS_PER_MINUTE) {
minutes = seconds / SECONDS_PER_MINUTE;
seconds -= minutes * SECONDS_PER_MINUTE;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fa86c74..8b804e8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2415,6 +2415,7 @@
* 1 PFLAG3_SCROLL_INDICATOR_START
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
+ * 1111111 PFLAG3_POINTER_ICON_MASK
* |-------|-------|-------|-------|
*/
@@ -2611,6 +2612,36 @@
static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
/**
+ * The mask for use with private flags indicating bits used for pointer icon shapes.
+ */
+ static final int PFLAG3_POINTER_ICON_MASK = 0x7f8000;
+
+ /**
+ * Left-shift used for pointer icon shape values in private flags.
+ */
+ static final int PFLAG3_POINTER_ICON_LSHIFT = 15;
+
+ /**
+ * Value indicating {@link PointerIcon.STYLE_NOT_SPECIFIED}.
+ */
+ private static final int PFLAG3_POINTER_ICON_NOT_SPECIFIED = 0 << PFLAG3_POINTER_ICON_LSHIFT;
+
+ /**
+ * Value indicating {@link PointerIcon.STYLE_NULL}.
+ */
+ private static final int PFLAG3_POINTER_ICON_NULL = 1 << PFLAG3_POINTER_ICON_LSHIFT;
+
+ /**
+ * Value incicating {@link PointerIcon.STYLE_CUSTOM}.
+ */
+ private static final int PFLAG3_POINTER_ICON_CUSTOM = 2 << PFLAG3_POINTER_ICON_LSHIFT;
+
+ /**
+ * The base value for other pointer icon shapes.
+ */
+ private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
+
+ /**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
*
@@ -5341,21 +5372,53 @@
}
/**
- * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
- * OnLongClickListener did not consume the event.
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event.
*
- * @return True if one of the above receivers consumed the event, false otherwise.
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
*/
public boolean performLongClick() {
+ return performLongClickInternal(false, 0, 0);
+ }
+
+ /**
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event,
+ * anchoring it to an (x,y) coordinate.
+ *
+ * @param x x coordinate of the anchoring touch event
+ * @param y y coordinate of the anchoring touch event
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
+ */
+ public boolean performLongClick(float x, float y) {
+ return performLongClickInternal(true, x, y);
+ }
+
+ /**
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event,
+ * optionally anchoring it to an (x,y) coordinate.
+ *
+ * @param isAnchored whether this long click is anchored to a touch event
+ * @param x x coordinate of the anchoring touch event, ignored if
+ * {@code isAnchored} is set to {@code false}
+ * @param y y coordinate of the anchoring touch event, ignored if
+ * {@code isAnchored} is set to {@code false}
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
+ */
+ private boolean performLongClickInternal(boolean isAnchored, float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
- ListenerInfo li = mListenerInfo;
+ final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
- handled = showContextMenu();
+ handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -10002,32 +10065,36 @@
* KeyEvent.Callback.onKeyDown()}: perform press of the view
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
* is released, if the view is enabled and clickable.
+ * <p>
+ * Key presses in software keyboards will generally NOT trigger this
+ * listener, although some may elect to do so in some situations. Do not
+ * rely on this to catch software key presses.
*
- * <p>Key presses in software keyboards will generally NOT trigger this listener,
- * although some may elect to do so in some situations. Do not rely on this to
- * catch software key presses.
- *
- * @param keyCode A key code that represents the button pressed, from
- * {@link android.view.KeyEvent}.
- * @param event The KeyEvent object that defines the button action.
+ * @param keyCode a key code that represents the button pressed, from
+ * {@link android.view.KeyEvent}
+ * @param event the KeyEvent object that defines the button action
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
- boolean result = false;
-
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
- // Long clickable items don't necessarily have to be clickable
- if (((mViewFlags & CLICKABLE) == CLICKABLE ||
- (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
- (event.getRepeatCount() == 0)) {
- setPressed(true);
- checkForLongClick(0);
+
+ // Long clickable items don't necessarily have to be clickable.
+ if (((mViewFlags & CLICKABLE) == CLICKABLE
+ || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+ && (event.getRepeatCount() == 0)) {
+ // For the purposes of menu anchoring and drawable hotspots,
+ // key events are considered to be at the center of the view.
+ final float x = getWidth() / 2f;
+ final float y = getHeight() / 2f;
+ setPressed(true, x, y);
+ checkForLongClick(0, x, y);
return true;
}
}
- return result;
+
+ return false;
}
/**
@@ -10555,7 +10622,7 @@
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
- checkForLongClick(0);
+ checkForLongClick(0, x, y);
}
break;
@@ -12889,6 +12956,10 @@
mPrivateFlags |= PFLAG_DIRTY;
+ // Release any resources in-case we don't end up drawing again
+ // as anything cached is no longer valid
+ resetDisplayList();
+
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
@@ -19992,13 +20063,14 @@
}
}
- private void checkForLongClick(int delayOffset) {
+ private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
+ mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
@@ -20997,7 +21069,40 @@
/** @hide */
public int getPointerShape(MotionEvent event, float x, float y) {
- return PointerIcon.STYLE_NOT_SPECIFIED;
+ final int value = (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK);
+ switch (value) {
+ case PFLAG3_POINTER_ICON_NOT_SPECIFIED:
+ return PointerIcon.STYLE_NOT_SPECIFIED;
+ case PFLAG3_POINTER_ICON_NULL:
+ return PointerIcon.STYLE_NULL;
+ case PFLAG3_POINTER_ICON_CUSTOM:
+ return PointerIcon.STYLE_CUSTOM;
+ default:
+ return ((value - PFLAG3_POINTER_ICON_VALUE_START) >> PFLAG3_POINTER_ICON_LSHIFT)
+ + PointerIcon.STYLE_ARROW;
+ }
+ }
+
+ /** @hide */
+ public void setPointerShape(int pointerShape) {
+ int newValue;
+ if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) {
+ newValue = PFLAG3_POINTER_ICON_NOT_SPECIFIED;
+ } else if (pointerShape == PointerIcon.STYLE_NULL) {
+ newValue = PFLAG3_POINTER_ICON_NULL;
+ } else if (pointerShape == PointerIcon.STYLE_CUSTOM) {
+ newValue = PFLAG3_POINTER_ICON_CUSTOM;
+ } else if (pointerShape >= PointerIcon.STYLE_ARROW
+ && pointerShape <= PointerIcon.STYLE_GRABBING) {
+ newValue = ((pointerShape - PointerIcon.STYLE_ARROW) << PFLAG3_POINTER_ICON_LSHIFT)
+ + PFLAG3_POINTER_ICON_VALUE_START;
+ } else {
+ Log.w(VIEW_LOG_TAG, "Invalid pointer shape " + pointerShape + " is specified.");
+ return;
+ }
+ if (newValue != (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK)) {
+ mPrivateFlags3 = (mPrivateFlags3 & ~PFLAG3_POINTER_ICON_MASK) | newValue;
+ }
}
//
@@ -21354,17 +21459,24 @@
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
+ private float mX;
+ private float mY;
@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
- if (performLongClick()) {
+ if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
+ public void setAnchor(float x, float y) {
+ mX = x;
+ mY = y;
+ }
+
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
@@ -21378,7 +21490,7 @@
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
- checkForLongClick(ViewConfiguration.getTapTimeout());
+ checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7c7ad91..db978a6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4435,6 +4435,10 @@
}
}
}
+
+ if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
+ notifyChildOfDragStart(child);
+ }
}
private void addInArray(View child, int index) {
@@ -4686,6 +4690,10 @@
mTransientIndices.set(i, oldIndex - 1);
}
}
+
+ if (mCurrentDragStartEvent != null) {
+ mChildrenInterestedInDrag.remove(view);
+ }
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7d39a0c..e17bdd7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4191,7 +4191,10 @@
if (mPointerIconShape != pointerShape) {
mPointerIconShape = pointerShape;
- event.getDevice().setPointerShape(pointerShape);
+ final InputDevice inputDevice = event.getDevice();
+ if (inputDevice != null) {
+ inputDevice.setPointerShape(pointerShape);
+ }
}
} else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1b17736..1735e1b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3083,8 +3083,32 @@
return "ACTION_PASTE";
case ACTION_SET_SELECTION:
return "ACTION_SET_SELECTION";
+ case ACTION_EXPAND:
+ return "ACTION_EXPAND";
+ case ACTION_COLLAPSE:
+ return "ACTION_COLLAPSE";
+ case ACTION_DISMISS:
+ return "ACTION_DISMISS";
+ case ACTION_SET_TEXT:
+ return "ACTION_SET_TEXT";
+ case R.id.accessibilityActionShowOnScreen:
+ return "ACTION_SHOW_ON_SCREEN";
+ case R.id.accessibilityActionScrollToPosition:
+ return "ACTION_SCROLL_TO_POSITION";
+ case R.id.accessibilityActionScrollUp:
+ return "ACTION_SCROLL_UP";
+ case R.id.accessibilityActionScrollLeft:
+ return "ACTION_SCROLL_LEFT";
+ case R.id.accessibilityActionScrollDown:
+ return "ACTION_SCROLL_DOWN";
+ case R.id.accessibilityActionScrollRight:
+ return "ACTION_SCROLL_RIGHT";
+ case R.id.accessibilityActionSetProgress:
+ return "ACTION_SET_PROGRESS";
+ case R.id.accessibilityActionContextClick:
+ return "ACTION_CONTEXT_CLICK";
default:
- return"ACTION_UNKNOWN";
+ return "ACTION_UNKNOWN";
}
}
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 0760d2b..23e9a0d 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -40,6 +40,13 @@
boolean isForMainFrame();
/**
+ * Gets whether the request was a result of a redirect.
+ *
+ * @return whether the request was a result of a redirect.
+ */
+ boolean isRedirect();
+
+ /**
* Gets whether a gesture (such as a click) was associated with the request.
* For security reasons in certain situations this method may return false even though the
* sequence of events which caused the request to be created was initiated by a user gesture.
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index f2bb55a..0e5034d 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -38,12 +38,42 @@
* @param url The url to be loaded.
* @return True if the host application wants to leave the current WebView
* and handle the url itself, otherwise return false.
+ * @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest)
+ * shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead.
*/
+ @Deprecated
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
/**
+ * Give the host application a chance to take over the control when a new
+ * url is about to be loaded in the current WebView. If WebViewClient is not
+ * provided, by default WebView will ask Activity Manager to choose the
+ * proper handler for the url. If WebViewClient is provided, return true
+ * means the host application handles the url, while return false means the
+ * current WebView handles the url.
+ *
+ * <p>Notes:
+ * <ul>
+ * <li>This method is not called for requests using the POST "method".</li>
+ * <li>This method is also called for subframes with non-http schemes, thus it is
+ * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)}
+ * with the request's url from inside the method and then return true,
+ * as this will make WebView to attempt loading a non-http url, and thus fail.</li>
+ * </ul>
+ * </p>
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param request Object containing the details of the request.
+ * @return True if the host application wants to leave the current WebView
+ * and handle the url itself, otherwise return false.
+ */
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
+ return shouldOverrideUrlLoading(view, request.getUrl().toString());
+ }
+
+ /**
* Notify the host application that a page has started loading. This method
* is called once for each main frame load so a page with iframes or
* framesets will call onPageStarted one time for the main frame. This also
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
index 67ee085..17e8c9c 100644
--- a/core/java/com/android/internal/alsa/AlsaCardsParser.java
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -94,6 +94,12 @@
public String textFormat() {
return mCardName + " : " + mCardDescription;
}
+
+ public void log(int listIndex) {
+ Slog.d(TAG, "" + listIndex +
+ " [" + mCardNum + " " + mCardName + " : " + mCardDescription +
+ " usb:" + mIsUsb);
+ }
}
public AlsaCardsParser() {}
@@ -169,9 +175,41 @@
// return -1 if none found
public int getDefaultUsbCard() {
+ // save the current list of devices
+ ArrayList<AlsaCardsParser.AlsaCardRecord> prevRecs = mCardRecords;
+ if (DEBUG) {
+ LogDevices("Previous Devices:", prevRecs);
+ }
+
+ // get the new list of devices
+ scan();
+ if (DEBUG) {
+ LogDevices("Current Devices:", mCardRecords);
+ }
+
+ // Calculate the difference between the old and new device list
+ ArrayList<AlsaCardRecord> newRecs = getNewCardRecords(prevRecs);
+ if (DEBUG) {
+ LogDevices("New Devices:", newRecs);
+ }
+
// Choose the most-recently added EXTERNAL card
+ // Check recently added devices
+ for (AlsaCardRecord rec : newRecs) {
+ if (DEBUG) {
+ Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
+ }
+ if (rec.mIsUsb) {
+ // Found it
+ return rec.mCardNum;
+ }
+ }
+
// or return the first added EXTERNAL card?
- for (AlsaCardRecord rec : mCardRecords) {
+ for (AlsaCardRecord rec : prevRecs) {
+ if (DEBUG) {
+ Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
+ }
if (rec.mIsUsb) {
return rec.mCardNum;
}
@@ -183,11 +221,17 @@
public int getDefaultCard() {
// return an external card if possible
int card = getDefaultUsbCard();
+ if (DEBUG) {
+ Slog.d(TAG, "getDefaultCard() default usb card:" + card);
+ }
if (card < 0 && getNumCardRecords() > 0) {
// otherwise return the (internal) card with the highest number
card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
}
+ if (DEBUG) {
+ Slog.d(TAG, " returns card:" + card);
+ }
return card;
}
@@ -222,4 +266,13 @@
}
}
}
+
+ static public void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) {
+ Slog.d(TAG, caption + " ----------------");
+ int listIndex = 0;
+ for (AlsaCardRecord device : deviceList) {
+ device.log(listIndex++);
+ }
+ Slog.d(TAG, "----------------");
+ }
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 8a26211..b3bd46d 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -5434,7 +5434,7 @@
* @Return Returns true if the window should show a non client decor.
**/
private static boolean hasNonClientDecor(int workspaceId) {
- return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
+ return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
}
/**
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 20d00b0..7b69c9e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -189,6 +189,7 @@
external/pdfium/core/include/fpdfdoc \
external/pdfium/fpdfsdk/include \
external/pdfium/public \
+ external/skia/include/private \
external/skia/src/core \
external/skia/src/effects \
external/skia/src/images \
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 28bc7fe..ecaf951 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -165,7 +165,7 @@
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();
- if (info.fColorType == kUnknown_SkColorType) {
+ if (info.colorType() == kUnknown_SkColorType) {
ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration");
return false;
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 93259e7..068517a 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -486,7 +486,7 @@
android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();
- if (info.fColorType == kUnknown_SkColorType) {
+ if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return NULL;
}
@@ -538,7 +538,7 @@
bool GraphicsJNI::allocatePixels(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();
- if (info.fColorType == kUnknown_SkColorType) {
+ if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return NULL;
}
@@ -581,7 +581,7 @@
int fd;
const SkImageInfo& info = bitmap->info();
- if (info.fColorType == kUnknown_SkColorType) {
+ if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return nullptr;
}
@@ -625,7 +625,7 @@
android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable, int fd, void* addr, bool readOnly) {
const SkImageInfo& info = bitmap->info();
- if (info.fColorType == kUnknown_SkColorType) {
+ if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
return nullptr;
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 24055e7..da96b93 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -313,12 +313,11 @@
return 0;
}
+
SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
convertPixelFormat(outBuffer.format),
- kPremul_SkAlphaType);
- if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
- info.fAlphaType = kOpaque_SkAlphaType;
- }
+ outBuffer.format == PIXEL_FORMAT_RGBX_8888 ?
+ kOpaque_SkAlphaType : kPremul_SkAlphaType);
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 65ebb663..931ad54 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -138,35 +138,36 @@
return NULL;
}
- SkImageInfo screenshotInfo;
- screenshotInfo.fWidth = screenshot->getWidth();
- screenshotInfo.fHeight = screenshot->getHeight();
-
+ SkColorType colorType;
+ SkAlphaType alphaType;
switch (screenshot->getFormat()) {
case PIXEL_FORMAT_RGBX_8888: {
- screenshotInfo.fColorType = kRGBA_8888_SkColorType;
- screenshotInfo.fAlphaType = kOpaque_SkAlphaType;
+ colorType = kRGBA_8888_SkColorType;
+ alphaType = kOpaque_SkAlphaType;
break;
}
case PIXEL_FORMAT_RGBA_8888: {
- screenshotInfo.fColorType = kRGBA_8888_SkColorType;
- screenshotInfo.fAlphaType = kPremul_SkAlphaType;
+ colorType = kRGBA_8888_SkColorType;
+ alphaType = kPremul_SkAlphaType;
break;
}
case PIXEL_FORMAT_RGB_565: {
- screenshotInfo.fColorType = kRGB_565_SkColorType;
- screenshotInfo.fAlphaType = kOpaque_SkAlphaType;
+ colorType = kRGB_565_SkColorType;
+ alphaType = kOpaque_SkAlphaType;
break;
}
default: {
return NULL;
}
}
+ SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
+ screenshot->getHeight(),
+ colorType, alphaType);
const size_t rowBytes =
screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
- if (!screenshotInfo.fWidth || !screenshotInfo.fHeight) {
+ if (!screenshotInfo.width() || !screenshotInfo.height()) {
return NULL;
}
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index b736a17..e185281 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -72,29 +72,25 @@
// FIXME: consider exporting this to share (e.g. android_view_Surface.cpp)
static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) {
- SkImageInfo info;
- info.fWidth = buffer.width;
- info.fHeight = buffer.height;
+ SkColorType colorType = kUnknown_SkColorType;
+ SkAlphaType alphaType = kOpaque_SkAlphaType;
switch (buffer.format) {
case WINDOW_FORMAT_RGBA_8888:
- info.fColorType = kN32_SkColorType;
- info.fAlphaType = kPremul_SkAlphaType;
+ colorType = kN32_SkColorType;
+ alphaType = kPremul_SkAlphaType;
break;
case WINDOW_FORMAT_RGBX_8888:
- info.fColorType = kN32_SkColorType;
- info.fAlphaType = kOpaque_SkAlphaType;
+ colorType = kN32_SkColorType;
+ alphaType = kOpaque_SkAlphaType;
break;
case WINDOW_FORMAT_RGB_565:
- info.fColorType = kRGB_565_SkColorType;
- info.fAlphaType = kOpaque_SkAlphaType;
+ colorType = kRGB_565_SkColorType;
+ alphaType = kOpaque_SkAlphaType;
break;
default:
- info.fColorType = kUnknown_SkColorType;
- // switch to kUnknown_SkAlphaType when its in skia
- info.fAlphaType = kOpaque_SkAlphaType;
break;
}
- return info;
+ return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType);
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 39eda58..5828829 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1476,7 +1476,7 @@
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|preinstalled|appop|pre23" />
+ android:protectionLevel="signature|preinstalled|appop|pre23|development" />
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 45e82ac..ff0fa9c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1029,7 +1029,7 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"タップしてUSBデバッグを無効化"</string>
<string name="select_input_method" msgid="8547250819326693584">"キーボードの変更"</string>
<string name="configure_input_methods" msgid="4769971288371946846">"キーボードの選択"</string>
- <string name="show_ime" msgid="9157568568695230830">"入力方法を表示する"</string>
+ <string name="show_ime" msgid="9157568568695230830">"スクリーンキーボードを表示する"</string>
<string name="hardware" msgid="7517821086888990278">"ハードウェア"</string>
<string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"キーボードレイアウトの選択"</string>
<string name="select_keyboard_layout_notification_message" msgid="4465907700449257063">"タップしてキーボードレイアウトを選択してください。"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 6c576ad..228f4ae 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -677,7 +677,7 @@
<string name="lockscreen_transport_stop_description" msgid="5907083260651210034">"Opriți"</string>
<string name="lockscreen_transport_rew_description" msgid="6944412838651990410">"Derulaţi"</string>
<string name="lockscreen_transport_ffw_description" msgid="42987149870928985">"Derulaţi rapid înainte"</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgenţă"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgență"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"Rețea blocată"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"Cardul SIM este blocat cu codul PUK."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Consultaţi Ghidul de utilizare sau contactaţi Serviciul de relaţii cu clienţii."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 960a314..7a3aab8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1006,14 +1006,14 @@
<string name="sms_short_code_confirm_never_allow" msgid="446992765774269673">"Asla İzin Verme"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"SIM kart çıkarıldı"</string>
<string name="sim_removed_message" msgid="5450336489923274918">"Hücresel ağ, geçerli bir SIM kart takıp cihazınızı yeniden başlatıncaya kadar kullanılamayacak."</string>
- <string name="sim_done_button" msgid="827949989369963775">"Tamamlandı"</string>
+ <string name="sim_done_button" msgid="827949989369963775">"Bitti"</string>
<string name="sim_added_title" msgid="3719670512889674693">"SIM kart eklendi"</string>
<string name="sim_added_message" msgid="7797975656153714319">"Hücresel ağa erişmek için cihazınızı yeniden başlatın."</string>
<string name="sim_restart_button" msgid="4722407842815232347">"Yeniden başlat"</string>
<string name="time_picker_dialog_title" msgid="8349362623068819295">"Saati ayarlayın"</string>
<string name="date_picker_dialog_title" msgid="5879450659453782278">"Tarihi ayarlayın"</string>
<string name="date_time_set" msgid="5777075614321087758">"Ayarla"</string>
- <string name="date_time_done" msgid="2507683751759308828">"Tamamlandı"</string>
+ <string name="date_time_done" msgid="2507683751759308828">"Bitti"</string>
<string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"YENİ: "</font></string>
<string name="perms_description_app" msgid="5139836143293299417">"Sağlayan: <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
<string name="no_permissions" msgid="7283357728219338112">"İzin gerektirmez"</string>
diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml
index e5991fc..dde8b2e 100644
--- a/core/res/res/values-watch/strings.xml
+++ b/core/res/res/values-watch/strings.xml
@@ -26,47 +26,4 @@
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. Override from base which says "Body Sensors". [CHAR_LIMIT=25] -->
<string name="permgrouplab_sensors">Sensors</string>
-
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_contactswear">access your contacts</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_locationwear">access this watch\'s location</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_calendarwear">access your calendar</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_smswear">send and view SMS messages</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_storagewear">access photos, media, and files on your watch</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_microphonewear">record audio</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_camerawear">take pictures and record video</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_phonewear">make and manage phone calls</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permgrouplab_sensorswear">access sensor data about your vital signs</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_statusBarServicewear">be the status bar</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_bodySensorswear">access body sensors (like heart rate monitors)</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_accessFineLocationwear">access precise location (GPS and network-based)</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_accessCoarseLocationwear">access approximate location (network-based)</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_sim_communicationwear">send commands to the SIM</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_createNetworkSocketswear">have full network access</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_manageProfileAndDeviceOwnerswear">manage profile and device owners</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_changeWimaxStatewear">change WiMAX state</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_handoverStatuswear">receive Android Beam transfer status</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_route_media_outputwear">route media output</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_readInstallSessionswear">read install sessions</string>
- <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] -->
- <string name="permlab_requestInstallPackageswear">request install packages</string>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 093ea80..d7dd3ec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5018,6 +5018,12 @@
<attr name="autoMirrored" format="boolean" />
</declare-styleable>
+ <!-- Drawable class used to wrap other drawables. -->
+ <declare-styleable name="DrawableWrapper">
+ <!-- The wrapped drawable. -->
+ <attr name="drawable" />
+ </declare-styleable>
+
<!-- Drawable used to render several states. Each state is represented by
a child drawable. -->
<declare-styleable name="StateListDrawable">
@@ -5385,6 +5391,7 @@
<attr name="color" />
</declare-styleable>
+ <!-- Drawable used to wrap and inset another drawable. -->
<declare-styleable name="InsetDrawable">
<attr name="visible" />
<attr name="drawable" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2621bc9c..dd42c22 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -367,7 +367,8 @@
<dimen name="resolver_max_width">480dp</dimen>
- <!-- Size of the offset applied to the position of the circular mask. This
+ <!-- @deprecated Use config_windowOutsetBottom instead.
+ Size of the offset applied to the position of the circular mask. This
is only used on circular displays. In the case where there is no
"chin", this will default to 0 -->
<dimen name="circular_display_mask_offset">0px</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8d53d7..7584d4d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -621,7 +621,7 @@
<string name="permdesc_statusBar">Allows the app to disable the status bar or add and remove system icons.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_statusBarService">status bar</string>
+ <string name="permlab_statusBarService">be the status bar</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_statusBarService">Allows the app to be the status bar.</string>
@@ -718,7 +718,7 @@
discover information about which applications are used on the device.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_manageProfileAndDeviceOwners">Manage profile and device owners</string>
+ <string name="permlab_manageProfileAndDeviceOwners">manage profile and device owners</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to set the profile/device owners.
[CHAR LIMIT=NONE] -->
<string name="permdesc_manageProfileAndDeviceOwners">Allows apps to set the profile owners and the device owner.</string>
@@ -882,7 +882,7 @@
Malicious apps may use this to erase or modify your call log.</string>
<!-- Title of the body sensors permission, listed so the user can decide whether to allow the application to access body sensor data. [CHAR LIMIT=30] -->
- <string name="permlab_bodySensors">body sensors (like heart rate monitors)
+ <string name="permlab_bodySensors">access body sensors (like heart rate monitors)
</string>
<!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
<string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors
@@ -936,7 +936,7 @@
with the operation of the GPS or other location sources.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_accessFineLocation">precise location (GPS and
+ <string name="permlab_accessFineLocation">access precise location (GPS and
network-based)</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessFineLocation">Allows the app to get your
@@ -947,7 +947,7 @@
battery power.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_accessCoarseLocation">approximate location
+ <string name="permlab_accessCoarseLocation">access approximate location
(network-based)</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessCoarseLocation">Allows the app to get your
@@ -970,7 +970,7 @@
without your confirmation.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_sim_communication">sim communication</string>
+ <string name="permlab_sim_communication">send commands to the SIM</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_sim_communication">Allows the app to send commands to the SIM. This is very dangerous.</string>
@@ -1077,7 +1077,7 @@
connected.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_createNetworkSockets">full network access</string>
+ <string name="permlab_createNetworkSockets">have full network access</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_createNetworkSockets">Allows the app to create
network sockets and use custom network protocols. The browser and other
@@ -1141,7 +1141,7 @@
WiMAX is enabled and information about any WiMAX networks that are
connected. </string>
- <string name="permlab_changeWimaxState">Change WiMAX state</string>
+ <string name="permlab_changeWimaxState">change WiMAX state</string>
<string name="permdesc_changeWimaxState" product="tablet">Allows the app to
connect the tablet to and disconnect the tablet from WiMAX networks.</string>
<string name="permdesc_changeWimaxState" product="tv">Allows the app to
@@ -1346,7 +1346,7 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
- <string name="permlab_handoverStatus">Receive Android Beam transfer status</string>
+ <string name="permlab_handoverStatus">receive Android Beam transfer status</string>
<string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -2975,17 +2975,17 @@
<string name="activity_list_empty">No matching activities found.</string>
<!-- Title of an application permission that lets an application route media output. -->
- <string name="permlab_route_media_output">Route media output</string>
+ <string name="permlab_route_media_output">route media output</string>
<!-- Description of an application permission that lets an application route media output. -->
<string name="permdesc_route_media_output">Allows an application to route media output to other external devices.</string>
<!-- Title of an application permission that lets it read install sessions. -->
- <string name="permlab_readInstallSessions">Read install sessions</string>
+ <string name="permlab_readInstallSessions">read install sessions</string>
<!-- Description of an application permission that lets it read install sessions. -->
<string name="permdesc_readInstallSessions">Allows an application to read install sessions. This allows it to see details about active package installations.</string>
<!-- Title of an application permission that lets it read install sessions. -->
- <string name="permlab_requestInstallPackages">Request install packages</string>
+ <string name="permlab_requestInstallPackages">request install packages</string>
<!-- Description of an application permission that lets it read install sessions. -->
<string name="permdesc_requestInstallPackages">Allows an application to request installation of packages.</string>
diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
index 80181cf..4c9b00a 100644
--- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.UserHandle;
import android.test.AndroidTestCase;
+import android.view.inputmethod.InputMethodInfo;
import java.util.Arrays;
import java.util.HashSet;
@@ -77,6 +78,27 @@
assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps);
}
+ public void testQueryAppsDefaultIme() {
+ // Test query default system IMEs
+ List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES,
+ true, UserHandle.of(UserHandle.myUserId()));
+ assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps);
+
+ // Test query default IMEs
+ apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+ UserHandle.of(UserHandle.myUserId()));
+ assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps);
+
+ // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process
+ try {
+ mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false,
+ UserHandle.of(UserHandle.USER_NULL));
+ fail("queryApps must fail if wrong user was passed");
+ } catch (IllegalArgumentException e) {
+ // OK
+ }
+ }
+
private class AppsQueryHelperTestable extends AppsQueryHelper {
public AppsQueryHelperTestable(Context context) {
@@ -121,6 +143,21 @@
p1.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
return Arrays.asList(p1);
}
+
+ @Override
+ protected List<InputMethodInfo> getInputMethodList() {
+ final ResolveInfo sysApp1 = new ResolveInfo();
+ sysApp1.serviceInfo = new ServiceInfo();
+ sysApp1.serviceInfo.packageName = "sys_app1";
+ sysApp1.serviceInfo.name = "name";
+ InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true);
+ final ResolveInfo app4 = new ResolveInfo();
+ app4.serviceInfo = new ServiceInfo();
+ app4.serviceInfo.packageName = "app4";
+ app4.serviceInfo.name = "name";
+ InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true);
+ return Arrays.asList(imi1, imi2);
+ }
}
private static void assertEqualsIgnoreOrder(List<String> expected, List<String> actual) {
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index b6b4f4f..d5f6321 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -561,9 +561,13 @@
assertTrue(v46lp.isProvisioned());
assertEquals(ProvisioningChange.STILL_PROVISIONED,
+ LinkProperties.compareProvisioning(v4lp, v46lp));
+ assertEquals(ProvisioningChange.STILL_PROVISIONED,
LinkProperties.compareProvisioning(v6lp, v46lp));
assertEquals(ProvisioningChange.LOST_PROVISIONING,
LinkProperties.compareProvisioning(v46lp, v6lp));
+ assertEquals(ProvisioningChange.LOST_PROVISIONING,
+ LinkProperties.compareProvisioning(v46lp, v4lp));
// Check that losing and gaining a secondary router does not change
// the provisioning status.
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
index 74c8e04..2370627 100644
--- a/core/tests/coretests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java
@@ -436,15 +436,17 @@
assertFormatDuration("+100ms", 100);
assertFormatDuration("+101ms", 101);
assertFormatDuration("+330ms", 330);
+ assertFormatDuration("+1s0ms", 1000);
assertFormatDuration("+1s330ms", 1330);
assertFormatDuration("+10s24ms", 10024);
+ assertFormatDuration("+1m0s30ms", 60030);
+ assertFormatDuration("+1h0m0s30ms", 3600030);
+ assertFormatDuration("+1d0h0m0s30ms", 86400030);
}
public void testFormatHugeDuration() {
- //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
- // TODO: improve formatDuration() API
- assertFormatDuration("+999d23h59m59s999ms", 1342833071555L);
- assertFormatDuration("-999d23h59m59s999ms", -1342833071555L);
+ assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
+ assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L);
}
private void assertFormatDuration(String expected, long duration) {
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
new file mode 100644
index 0000000..c5e2ae6
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget;
+
+import static android.widget.espresso.TextViewActions.mouseDragOnText;
+import static android.widget.espresso.TextViewAssertions.hasSelection;
+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.typeTextIntoFocusedView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests mouse interaction of the TextView widget from an Activity
+ */
+public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
+
+ public TextViewActivityMouseTest() {
+ super(TextViewActivity.class);
+ }
+
+ @SmallTest
+ public void testSelectTextByDrag() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDrag_reverse() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText( helloWorld.indexOf("ld!"), helloWorld.indexOf("llo")));
+
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
index a0cd848..9ff8e82 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
@@ -20,25 +20,22 @@
import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static com.android.internal.util.Preconditions.checkNotNull;
import static org.hamcrest.Matchers.allOf;
-
import android.annotation.Nullable;
import android.os.SystemClock;
import android.support.test.espresso.UiController;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
-import android.support.test.espresso.action.GeneralClickAction;
import android.support.test.espresso.action.MotionEvents;
import android.support.test.espresso.action.PrecisionDescriber;
-import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Swiper;
-import android.support.test.espresso.action.Tap;
import android.support.test.espresso.util.HumanReadables;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.TextView;
+
import org.hamcrest.Matcher;
@@ -51,11 +48,48 @@
* <ul>
*/
public final class DragOnTextViewActions implements ViewAction {
+ public interface Dragger extends Swiper {
+ UiController wrapUiController(UiController uiController);
+ }
/**
* Executes different "drag on text" types to given positions.
*/
- public enum Drag implements Swiper {
+ public enum Drag implements Dragger {
+
+ /**
+ * Starts a drag with a mouse down.
+ */
+ MOUSE_DOWN {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ return downEvent;
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse down and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
/**
* Starts a drag with a long-press.
@@ -197,6 +231,11 @@
return res;
}
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return uiController;
+ }
}
/**
@@ -215,13 +254,13 @@
MotionEvent perform(UiController uiController, float[] coordinates, float[] precision);
}
- private final Swiper mDragger;
+ private final Dragger mDragger;
private final CoordinatesProvider mStartCoordinatesProvider;
private final CoordinatesProvider mEndCoordinatesProvider;
private final PrecisionDescriber mPrecisionDescriber;
public DragOnTextViewActions(
- Swiper dragger,
+ Dragger dragger,
CoordinatesProvider startCoordinatesProvider,
CoordinatesProvider endCoordinatesProvider,
PrecisionDescriber precisionDescriber) {
@@ -242,6 +281,8 @@
checkNotNull(uiController);
checkNotNull(view);
+ uiController = mDragger.wrapUiController(uiController);
+
float[] startCoordinates = mStartCoordinatesProvider.calculateCoordinates(view);
float[] endCoordinates = mEndCoordinatesProvider.calculateCoordinates(view);
float[] precision = mPrecisionDescriber.describePrecision();
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
new file mode 100644
index 0000000..f1387f8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import android.support.test.espresso.InjectEventSecurityException;
+import android.support.test.espresso.UiController;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE.
+ * Note that this doesn't change the tool type.
+ */
+public class MouseUiController implements UiController {
+ private final UiController mUiController;
+
+ public MouseUiController(UiController uiController) {
+ mUiController = uiController;
+ }
+
+ @Override
+ public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException {
+ return mUiController.injectKeyEvent(event);
+ }
+
+ @Override
+ public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException {
+ // Modify the event to mimic mouse primary button event.
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return mUiController.injectMotionEvent(event);
+ }
+
+ @Override
+ public boolean injectString(String str) throws InjectEventSecurityException {
+ return mUiController.injectString(str);
+ }
+
+ @Override
+ public void loopMainThreadForAtLeast(long millisDelay) {
+ mUiController.loopMainThreadForAtLeast(millisDelay);
+ }
+
+ @Override
+ public void loopMainThreadUntilIdle() {
+ mUiController.loopMainThreadUntilIdle();
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 835b1b9..4f5a72b 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -124,6 +124,27 @@
}
/**
+ * Returns an action that click then drags by mouse on text from startIndex to endIndex on the
+ * TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragOnTextViewActions(
+ DragOnTextViewActions.Drag.MOUSE_DOWN,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT));
+ }
+
+ /**
* A provider of the x, y coordinates of the text at the specified index in a text view.
*/
private static final class TextCoordinates implements CoordinatesProvider {
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index 3bd17fa..abcccbd 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -99,7 +99,7 @@
/** @hide */
public void printShortString(PrintWriter pw) {
- pw.println("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
+ pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
}
/**
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index 4fc5ede..971a3a2 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -50,7 +50,7 @@
* Creates a new animated rotating drawable with no wrapped drawable.
*/
public AnimatedRotateDrawable() {
- this(new AnimatedRotateState(null), null);
+ this(new AnimatedRotateState(null, null), null);
}
@Override
@@ -126,58 +126,22 @@
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable);
- super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
+ super.inflate(r, parser, attrs, theme);
updateStateFromTypedArray(a);
- inflateChildDrawable(r, parser, attrs, theme);
verifyRequiredAttributes(a);
a.recycle();
updateLocalState();
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
- // If we're not waiting on a theme, verify required attributes.
- if (getDrawable() == null && (mState.mThemeAttrs == null
- || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
- throw new XmlPullParserException(a.getPositionDescription()
- + ": <animated-rotate> tag requires a 'drawable' attribute or "
- + "child tag defining a drawable");
- }
- }
-
@Override
- void updateStateFromTypedArray(TypedArray a) {
- super.updateStateFromTypedArray(a);
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
final AnimatedRotateState state = mState;
-
- if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
- final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
- state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
- state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
- }
-
- if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
- final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
- state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
- state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
- }
-
- setFramesCount(a.getInt(
- R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
- setFramesDuration(a.getInt(
- R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
-
- final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable);
- if (dr != null) {
- setDrawable(dr);
- }
- }
-
- @Override
- public void applyTheme(@Nullable Theme t) {
- final AnimatedRotateState state = mState;
if (state == null) {
return;
}
@@ -195,13 +159,49 @@
}
}
- // The drawable may have changed as a result of applying the theme, so
- // apply the theme to the wrapped drawable last.
- super.applyTheme(t);
-
updateLocalState();
}
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (getDrawable() == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <animated-rotate> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ }
+
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
+ final AnimatedRotateState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
+ final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
+ state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+ state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ }
+
+ if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
+ final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
+ state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+ state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ }
+
+ setFramesCount(a.getInt(
+ R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
+ setFramesDuration(a.getInt(
+ R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
+ }
+
public void setFramesCount(int framesCount) {
mState.mFramesCount = framesCount;
mIncrement = 360.0f / mState.mFramesCount;
@@ -211,7 +211,15 @@
mState.mFrameDuration = framesDuration;
}
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ mState = new AnimatedRotateState(mState, null);
+ return mState;
+ }
+
static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState {
+ private int[] mThemeAttrs;
+
boolean mPivotXRel = false;
float mPivotX = 0;
boolean mPivotYRel = false;
@@ -219,8 +227,8 @@
int mFrameDuration = 150;
int mFramesCount = 12;
- public AnimatedRotateState(AnimatedRotateState orig) {
- super(orig);
+ public AnimatedRotateState(AnimatedRotateState orig, Resources res) {
+ super(orig, res);
if (orig != null) {
mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fccd0..cdd336d 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -21,6 +21,8 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -59,7 +61,7 @@
private ClipState mState;
ClipDrawable() {
- this(new ClipState(null), null);
+ this(new ClipState(null, null), null);
}
/**
@@ -72,7 +74,7 @@
* {@link #VERTICAL}
*/
public ClipDrawable(Drawable drawable, int gravity, int orientation) {
- this(new ClipState(null), null);
+ this(new ClipState(null, null), null);
mState.mGravity = gravity;
mState.mOrientation = orientation;
@@ -81,46 +83,24 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
updateStateFromTypedArray(a);
- inflateChildDrawable(r, parser, attrs, theme);
verifyRequiredAttributes(a);
a.recycle();
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
- // If we're not waiting on a theme, verify required attributes.
- if (getDrawable() == null && (mState.mThemeAttrs == null
- || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
- throw new XmlPullParserException(a.getPositionDescription()
- + ": <clip> tag requires a 'drawable' attribute or "
- + "child tag defining a drawable");
- }
- }
-
@Override
- void updateStateFromTypedArray(TypedArray a) {
- super.updateStateFromTypedArray(a);
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
final ClipState state = mState;
- state.mOrientation = a.getInt(
- R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
- state.mGravity = a.getInt(
- R.styleable.ClipDrawable_gravity, state.mGravity);
-
- final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
- if (dr != null) {
- setDrawable(dr);
- }
- }
-
- @Override
- public void applyTheme(Theme t) {
- final ClipState state = mState;
if (state == null) {
return;
}
@@ -136,10 +116,34 @@
a.recycle();
}
}
+ }
- // The drawable may have changed as a result of applying the theme, so
- // apply the theme to the wrapped drawable last.
- super.applyTheme(t);
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (getDrawable() == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <clip> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ }
+
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
+ final ClipState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ state.mOrientation = a.getInt(
+ R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
+ state.mGravity = a.getInt(
+ R.styleable.ClipDrawable_gravity, state.mGravity);
}
@Override
@@ -200,12 +204,20 @@
}
}
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ mState = new ClipState(mState, null);
+ return mState;
+ }
+
static final class ClipState extends DrawableWrapper.DrawableWrapperState {
+ private int[] mThemeAttrs;
+
int mOrientation = HORIZONTAL;
int mGravity = Gravity.LEFT;
- ClipState(ClipState orig) {
- super(orig);
+ ClipState(ClipState orig, Resources res) {
+ super(orig, res);
if (orig != null) {
mOrientation = orig.mOrientation;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b95c183..ff28777 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -55,6 +55,8 @@
import java.util.Arrays;
import java.util.Collection;
+import com.android.internal.R;
+
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
* often you will deal with Drawable as the type of resource retrieved for
@@ -791,8 +793,10 @@
/**
* Applies the specified theme to this Drawable and its children.
+ *
+ * @param t the theme to apply
*/
- public void applyTheme(@SuppressWarnings("unused") Theme t) {
+ public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) {
}
public boolean canApplyTheme() {
@@ -1177,8 +1181,8 @@
*
* @see #inflate(Resources, XmlPullParser, AttributeSet, Theme)
*/
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
- throws XmlPullParserException, IOException {
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
inflate(r, parser, attrs, null);
}
@@ -1192,17 +1196,11 @@
* @throws XmlPullParserException
* @throws IOException
*/
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- final TypedArray a;
- if (theme != null) {
- a = theme.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.Drawable, 0, 0);
- } else {
- a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable);
- }
-
- inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
+ mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
a.recycle();
}
@@ -1212,8 +1210,8 @@
* @throws XmlPullParserException
* @throws IOException
*/
- void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr)
- throws XmlPullParserException, IOException {
+ void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException {
mVisible = attrs.getBoolean(visibleAttr, mVisible);
}
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 9185e1a..c427870 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -23,6 +25,7 @@
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -33,6 +36,7 @@
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.view.View;
import java.io.IOException;
@@ -112,7 +116,66 @@
return mDrawable;
}
- void updateStateFromTypedArray(TypedArray a) {
+ @Override
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ final DrawableWrapperState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // The density may have changed since the last update. This will
+ // apply scaling to any existing constant state properties.
+ final int densityDpi = r.getDisplayMetrics().densityDpi;
+ final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.setDensity(targetDensity);
+
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper);
+ updateStateFromTypedArray(a);
+ a.recycle();
+
+ inflateChildDrawable(r, parser, attrs, theme);
+ }
+
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ // If we load the drawable later as part of updating from the typed
+ // array, it will already be themed correctly. So, we can theme the
+ // local drawable first.
+ if (mDrawable != null && mDrawable.canApplyTheme()) {
+ mDrawable.applyTheme(t);
+ }
+
+ final DrawableWrapperState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+ final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.setDensity(density);
+
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ state.mThemeAttrs, R.styleable.DrawableWrapper);
+ updateStateFromTypedArray(a);
+ a.recycle();
+ }
+ }
+
+ /**
+ * Updates constant state properties from the provided typed array.
+ * <p>
+ * Implementing subclasses should call through to the super method first.
+ *
+ * @param a the typed array rom which properties should be read
+ */
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
final DrawableWrapperState state = mState;
if (state == null) {
return;
@@ -124,20 +187,8 @@
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
- // TODO: Consider using R.styleable.DrawableWrapper_drawable
- }
-
- @Override
- public void applyTheme(Resources.Theme t) {
- super.applyTheme(t);
-
- final DrawableWrapperState state = mState;
- if (state == null) {
- return;
- }
-
- if (mDrawable != null && mDrawable.canApplyTheme()) {
- mDrawable.applyTheme(t);
+ if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) {
+ setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable));
}
}
@@ -371,8 +422,9 @@
* child element will take precedence over any other child elements or
* explicit drawable attribute.
*/
- void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs,
- Resources.Theme theme) throws XmlPullParserException, IOException {
+ private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
// Seek to the first child element.
Drawable dr = null;
int type;
@@ -390,17 +442,61 @@
}
abstract static class DrawableWrapperState extends Drawable.ConstantState {
- int[] mThemeAttrs;
+ private int[] mThemeAttrs;
+
int mChangingConfigurations;
+ int mDensity = DisplayMetrics.DENSITY_DEFAULT;
Drawable.ConstantState mDrawableState;
- DrawableWrapperState(DrawableWrapperState orig) {
+ DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
if (orig != null) {
mThemeAttrs = orig.mThemeAttrs;
mChangingConfigurations = orig.mChangingConfigurations;
mDrawableState = orig.mDrawableState;
}
+
+ final int density;
+ if (res != null) {
+ density = res.getDisplayMetrics().densityDpi;
+ } else if (orig != null) {
+ density = orig.mDensity;
+ } else {
+ density = 0;
+ }
+
+ mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+ }
+
+ /**
+ * Sets the constant state density.
+ * <p>
+ * If the density has been previously set, dispatches the change to
+ * subclasses so that density-dependent properties may be scaled as
+ * necessary.
+ *
+ * @param targetDensity the new constant state density
+ */
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
+
+ onDensityChanged(sourceDensity, targetDensity);
+ }
+ }
+
+ /**
+ * Called when the constant state density changes.
+ * <p>
+ * Subclasses with density-dependent constant state properties should
+ * override this method and scale their properties as necessary.
+ *
+ * @param sourceDensity the previous constant state density
+ * @param targetDensity the new constant state density
+ */
+ void onDensityChanged(int sourceDensity, int targetDensity) {
+ // Stub method.
}
@Override
@@ -425,7 +521,7 @@
}
@Override
- public abstract Drawable newDrawable(Resources res);
+ public abstract Drawable newDrawable(@Nullable Resources res);
@Override
public int getChangingConfigurations() {
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index e1ebdbb..927b9c9 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -22,14 +22,17 @@
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import java.io.IOException;
@@ -58,7 +61,7 @@
* No-arg constructor used by drawable inflation.
*/
InsetDrawable() {
- this(new InsetState(null), null);
+ this(new InsetState(null, null), null);
}
/**
@@ -67,7 +70,7 @@
* @param drawable The drawable to inset.
* @param inset Inset in pixels around the drawable.
*/
- public InsetDrawable(Drawable drawable, int inset) {
+ public InsetDrawable(@Nullable Drawable drawable, int inset) {
this(drawable, inset, inset, inset, inset);
}
@@ -80,9 +83,9 @@
* @param insetRight Right inset in pixels.
* @param insetBottom Bottom inset in pixels.
*/
- public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight,
- int insetBottom) {
- this(new InsetState(null), null);
+ public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop,
+ int insetRight, int insetBottom) {
+ this(new InsetState(null, null), null);
mState.mInsetLeft = insetLeft;
mState.mInsetTop = insetTop;
@@ -93,70 +96,24 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
updateStateFromTypedArray(a);
- inflateChildDrawable(r, parser, attrs, theme);
verifyRequiredAttributes(a);
a.recycle();
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
- // If we're not waiting on a theme, verify required attributes.
- if (getDrawable() == null && (mState.mThemeAttrs == null
- || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
- throw new XmlPullParserException(a.getPositionDescription()
- + ": <inset> tag requires a 'drawable' attribute or "
- + "child tag defining a drawable");
- }
- }
-
@Override
- void updateStateFromTypedArray(TypedArray a) {
- super.updateStateFromTypedArray(a);
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
final InsetState state = mState;
- final int N = a.getIndexCount();
- for (int i = 0; i < N; i++) {
- final int attr = a.getIndex(i);
- switch (attr) {
- case R.styleable.InsetDrawable_drawable:
- final Drawable dr = a.getDrawable(attr);
- if (dr != null) {
- setDrawable(dr);
- }
- break;
- case R.styleable.InsetDrawable_inset:
- final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
- if (inset != Integer.MIN_VALUE) {
- state.mInsetLeft = inset;
- state.mInsetTop = inset;
- state.mInsetRight = inset;
- state.mInsetBottom = inset;
- }
- break;
- case R.styleable.InsetDrawable_insetLeft:
- state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
- break;
- case R.styleable.InsetDrawable_insetTop:
- state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
- break;
- case R.styleable.InsetDrawable_insetRight:
- state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
- break;
- case R.styleable.InsetDrawable_insetBottom:
- state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
- break;
- }
- }
- }
-
- @Override
- public void applyTheme(Theme t) {
- final InsetState state = mState;
if (state == null) {
return;
}
@@ -172,10 +129,47 @@
a.recycle();
}
}
+ }
- // The drawable may have changed as a result of applying the theme, so
- // apply the theme to the wrapped drawable last.
- super.applyTheme(t);
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (getDrawable() == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <inset> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ }
+
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
+ final InsetState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ // Inset attribute may be overridden by more specific attributes.
+ if (a.hasValue(R.styleable.InsetDrawable_inset)) {
+ final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0);
+ state.mInsetLeft = inset;
+ state.mInsetTop = inset;
+ state.mInsetRight = inset;
+ state.mInsetBottom = inset;
+ }
+
+ state.mInsetLeft = a.getDimensionPixelOffset(
+ R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
+ state.mInsetRight = a.getDimensionPixelOffset(
+ R.styleable.InsetDrawable_insetRight, state.mInsetRight);
+ state.mInsetTop = a.getDimensionPixelOffset(
+ R.styleable.InsetDrawable_insetTop, state.mInsetTop);
+ state.mInsetBottom = a.getDimensionPixelOffset(
+ R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
}
@Override
@@ -243,30 +237,72 @@
@Override
DrawableWrapperState mutateConstantState() {
- mState = new InsetState(mState);
+ mState = new InsetState(mState, null);
return mState;
}
static final class InsetState extends DrawableWrapper.DrawableWrapperState {
+ private int[] mThemeAttrs;
+
int mInsetLeft = 0;
int mInsetTop = 0;
int mInsetRight = 0;
int mInsetBottom = 0;
- InsetState(InsetState orig) {
- super(orig);
+ InsetState(@Nullable InsetState orig, @Nullable Resources res) {
+ super(orig, res);
if (orig != null) {
mInsetLeft = orig.mInsetLeft;
mInsetTop = orig.mInsetTop;
mInsetRight = orig.mInsetRight;
mInsetBottom = orig.mInsetBottom;
+
+ if (orig.mDensity != mDensity) {
+ applyDensityScaling(orig.mDensity, mDensity);
+ }
}
}
@Override
- public Drawable newDrawable(Resources res) {
- return new InsetDrawable(this, res);
+ void onDensityChanged(int sourceDensity, int targetDensity) {
+ super.onDensityChanged(sourceDensity, targetDensity);
+
+ applyDensityScaling(sourceDensity, targetDensity);
+ }
+
+ /**
+ * Called when the constant state density changes to scale
+ * density-dependent properties specific to insets.
+ *
+ * @param sourceDensity the previous constant state density
+ * @param targetDensity the new constant state density
+ */
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity);
+ mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity);
+ mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity);
+ mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ // If this drawable is being created for a different density,
+ // just create a new constant state and call it a day.
+ final InsetState state;
+ if (res != null) {
+ final int densityDpi = res.getDisplayMetrics().densityDpi;
+ final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ if (density != mDensity) {
+ state = new InsetState(this, res);
+ } else {
+ state = this;
+ }
+ } else {
+ state = this;
+ }
+
+ return new InsetDrawable(state, res);
}
}
@@ -274,7 +310,7 @@
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private InsetDrawable(InsetState state, Resources res) {
+ private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) {
super(state, res);
mState = state;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 1a0ba6f..4368fe9 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -30,6 +30,7 @@
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.view.Gravity;
import android.view.View;
@@ -124,7 +125,7 @@
final int length = layers.length;
final ChildDrawable[] r = new ChildDrawable[length];
for (int i = 0; i < length; i++) {
- r[i] = new ChildDrawable();
+ r[i] = new ChildDrawable(mLayerState.mDensity);
r[i].mDrawable = layers[i];
layers[i].setCallback(this);
mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
@@ -140,6 +141,10 @@
this((LayerState) null, null);
}
+ /**
+ * The one constructor to rule them all. This is called by all public
+ * constructors to set the state and initialize local properties.
+ */
LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
if (mLayerState.mNum > 0) {
@@ -153,24 +158,137 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ // The density may have changed since the last update. This will
+ // apply scaling to any existing constant state properties.
+ final int densityDpi = r.getDisplayMetrics().densityDpi;
+ final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.setDensity(density);
+
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
updateStateFromTypedArray(a);
a.recycle();
+ final ChildDrawable[] array = state.mChildren;
+ final int N = state.mNum;
+ for (int i = 0; i < N; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+ }
+
inflateLayers(r, parser, attrs, theme);
ensurePadding();
refreshPadding();
}
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+ final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+ state.setDensity(density);
+
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ state.mThemeAttrs, R.styleable.LayerDrawable);
+ updateStateFromTypedArray(a);
+ a.recycle();
+ }
+
+ final ChildDrawable[] array = state.mChildren;
+ final int N = state.mNum;
+ for (int i = 0; i < N; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+
+ if (layer.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ layer.mThemeAttrs, R.styleable.LayerDrawableItem);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+ }
+
+ final Drawable d = layer.mDrawable;
+ if (d != null && d.canApplyTheme()) {
+ d.applyTheme(t);
+
+ // Update cached mask of child changing configurations.
+ state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+ }
+ }
+ }
+
+ /**
+ * Inflates child layers using the specified parser.
+ */
+ private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final LayerState state = mLayerState;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final ChildDrawable layer = new ChildDrawable(state.mDensity);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+ layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ if (layer.mDrawable != null) {
+ state.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ layer.mDrawable.setCallback(this);
+ }
+
+ addLayer(layer);
+ }
+ }
+
/**
* Initializes the constant state from the values in the typed array.
*/
- private void updateStateFromTypedArray(TypedArray a) {
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
final LayerState state = mLayerState;
// Account for any configuration changes.
@@ -181,7 +299,7 @@
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
- int attr = a.getIndex(i);
+ final int attr = a.getIndex(i);
switch (attr) {
case R.styleable.LayerDrawable_opacity:
state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
@@ -214,57 +332,7 @@
}
}
- /**
- * Inflates child layers using the specified parser.
- */
- private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
- throws XmlPullParserException, IOException {
- final LayerState state = mLayerState;
-
- final int innerDepth = parser.getDepth() + 1;
- int type;
- int depth;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- if (depth > innerDepth || !parser.getName().equals("item")) {
- continue;
- }
-
- final ChildDrawable layer = new ChildDrawable();
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
- updateLayerFromTypedArray(layer, a);
- a.recycle();
-
- // If the layer doesn't have a drawable or unresolved theme
- // attribute for a drawable, attempt to parse one from the child
- // element.
- if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
- layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
- while ((type = parser.next()) == XmlPullParser.TEXT) {
- }
- if (type != XmlPullParser.START_TAG) {
- throw new XmlPullParserException(parser.getPositionDescription()
- + ": <item> tag requires a 'drawable' attribute or "
- + "child tag defining a drawable");
- }
- layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
- }
-
- if (layer.mDrawable != null) {
- state.mChildrenChangingConfigurations |=
- layer.mDrawable.getChangingConfigurations();
- layer.mDrawable.setCallback(this);
- }
-
- addLayer(layer);
- }
- }
-
- private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) {
+ private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
final LayerState state = mLayerState;
// Account for any configuration changes.
@@ -273,25 +341,42 @@
// Extract the theme attributes, if any.
layer.mThemeAttrs = a.extractThemeAttrs();
- layer.mInsetL = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_left, layer.mInsetL);
- layer.mInsetT = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_top, layer.mInsetT);
- layer.mInsetR = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_right, layer.mInsetR);
- layer.mInsetB = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_bottom, layer.mInsetB);
- layer.mInsetS = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_start, layer.mInsetS);
- layer.mInsetE = a.getDimensionPixelOffset(
- R.styleable.LayerDrawableItem_end, layer.mInsetE);
- layer.mWidth = a.getDimensionPixelSize(
- R.styleable.LayerDrawableItem_width, layer.mWidth);
- layer.mHeight = a.getDimensionPixelSize(
- R.styleable.LayerDrawableItem_height, layer.mHeight);
- layer.mGravity = a.getInteger(
- R.styleable.LayerDrawableItem_gravity, layer.mGravity);
- layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId);
+ final int N = a.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ final int attr = a.getIndex(i);
+ switch (attr) {
+ case R.styleable.LayerDrawableItem_left:
+ layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
+ break;
+ case R.styleable.LayerDrawableItem_top:
+ layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
+ break;
+ case R.styleable.LayerDrawableItem_right:
+ layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
+ break;
+ case R.styleable.LayerDrawableItem_bottom:
+ layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
+ break;
+ case R.styleable.LayerDrawableItem_start:
+ layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
+ break;
+ case R.styleable.LayerDrawableItem_end:
+ layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
+ break;
+ case R.styleable.LayerDrawableItem_width:
+ layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
+ break;
+ case R.styleable.LayerDrawableItem_height:
+ layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
+ break;
+ case R.styleable.LayerDrawableItem_gravity:
+ layer.mGravity = a.getInteger(attr, layer.mGravity);
+ break;
+ case R.styleable.LayerDrawableItem_id:
+ layer.mId = a.getResourceId(attr, layer.mId);
+ break;
+ }
+ }
final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
if (dr != null) {
@@ -300,44 +385,6 @@
}
@Override
- public void applyTheme(Theme t) {
- super.applyTheme(t);
-
- final LayerState state = mLayerState;
- if (state == null) {
- return;
- }
-
- if (state.mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
- }
-
- final ChildDrawable[] array = state.mChildren;
- final int N = state.mNum;
- for (int i = 0; i < N; i++) {
- final ChildDrawable layer = array[i];
- if (layer.mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(layer.mThemeAttrs,
- R.styleable.LayerDrawableItem);
- updateLayerFromTypedArray(layer, a);
- a.recycle();
- }
-
- final Drawable d = layer.mDrawable;
- if (d != null && d.canApplyTheme()) {
- d.applyTheme(t);
-
- // Update cached mask of child changing configurations.
- state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
- }
- }
-
- ensurePadding();
- }
-
- @Override
public boolean canApplyTheme() {
return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
}
@@ -368,7 +415,7 @@
* @param layer The layer to add.
* @return The index of the layer.
*/
- int addLayer(ChildDrawable layer) {
+ int addLayer(@NonNull ChildDrawable layer) {
final LayerState st = mLayerState;
final int N = st.mChildren != null ? st.mChildren.length : 0;
final int i = st.mNum;
@@ -418,7 +465,7 @@
}
private ChildDrawable createLayer(Drawable dr) {
- final ChildDrawable layer = new ChildDrawable();
+ final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
layer.mDrawable = dr;
return layer;
}
@@ -1708,6 +1755,7 @@
static class ChildDrawable {
public Drawable mDrawable;
public int[] mThemeAttrs;
+ public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
public int mInsetL, mInsetT, mInsetR, mInsetB;
public int mInsetS = UNDEFINED_INSET;
public int mInsetE = UNDEFINED_INSET;
@@ -1716,11 +1764,12 @@
public int mGravity = Gravity.NO_GRAVITY;
public int mId = View.NO_ID;
- ChildDrawable() {
- // Default empty constructor.
+ ChildDrawable(int density) {
+ mDensity = density;
}
- ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) {
+ ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner,
+ @Nullable Resources res) {
final Drawable dr = orig.mDrawable;
final Drawable clone;
if (dr != null) {
@@ -1750,19 +1799,58 @@
mHeight = orig.mHeight;
mGravity = orig.mGravity;
mId = orig.mId;
+
+ final int densityDpi = res == null ? orig.mDensity : res.getDisplayMetrics().densityDpi;
+ mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+
+ if (orig.mDensity != mDensity) {
+ applyDensityScaling(orig.mDensity, mDensity);
+ }
}
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mDrawable != null && mDrawable.canApplyTheme());
}
+
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
+
+ applyDensityScaling(sourceDensity, targetDensity);
+ }
+ }
+
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ mInsetL = Bitmap.scaleFromDensity(mInsetL, sourceDensity, targetDensity);
+ mInsetT = Bitmap.scaleFromDensity(mInsetT, sourceDensity, targetDensity);
+ mInsetR = Bitmap.scaleFromDensity(mInsetR, sourceDensity, targetDensity);
+ mInsetB = Bitmap.scaleFromDensity(mInsetB, sourceDensity, targetDensity);
+ if (mInsetS != UNDEFINED_INSET) {
+ mInsetS = Bitmap.scaleFromDensity(mInsetS, sourceDensity, targetDensity);
+ }
+ if (mInsetE != UNDEFINED_INSET) {
+ mInsetE = Bitmap.scaleFromDensity(mInsetE, sourceDensity, targetDensity);
+ }
+ if (mWidth > 0) {
+ mWidth = Bitmap.scaleFromDensity(mWidth, sourceDensity, targetDensity);
+ }
+ if (mHeight > 0) {
+ mHeight = Bitmap.scaleFromDensity(mHeight, sourceDensity, targetDensity);
+ }
+ }
}
static class LayerState extends ConstantState {
+ private int[] mThemeAttrs;
+
int mNum;
ChildDrawable[] mChildren;
- int[] mThemeAttrs;
+ int mDensity;
+
+ // These values all correspond to mDensity.
int mPaddingTop = -1;
int mPaddingBottom = -1;
int mPaddingLeft = -1;
@@ -1784,7 +1872,19 @@
private int mPaddingMode = PADDING_MODE_NEST;
- LayerState(LayerState orig, LayerDrawable owner, Resources res) {
+ LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
+ @Nullable Resources res) {
+ final int densityDpi;
+ if (res != null) {
+ densityDpi = res.getDisplayMetrics().densityDpi;
+ } else if (orig != null) {
+ densityDpi = orig.mDensity;
+ } else {
+ densityDpi = 0;
+ }
+
+ mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+
if (orig != null) {
final ChildDrawable[] origChildDrawable = orig.mChildren;
final int N = orig.mNum;
@@ -1814,12 +1914,56 @@
mPaddingStart = orig.mPaddingStart;
mPaddingEnd = orig.mPaddingEnd;
mOpacityOverride = orig.mOpacityOverride;
+
+ if (orig.mDensity != mDensity) {
+ applyDensityScaling(orig.mDensity, mDensity);
+ }
} else {
mNum = 0;
mChildren = null;
}
}
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
+
+ onDensityChanged(sourceDensity, targetDensity);
+ }
+ }
+
+ protected void onDensityChanged(int sourceDensity, int targetDensity) {
+ applyDensityScaling(sourceDensity, targetDensity);
+ }
+
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ if (mPaddingLeft > 0) {
+ mPaddingLeft = Bitmap.scaleFromDensity(
+ mPaddingLeft, sourceDensity, targetDensity);
+ }
+ if (mPaddingTop > 0) {
+ mPaddingTop = Bitmap.scaleFromDensity(
+ mPaddingTop, sourceDensity, targetDensity);
+ }
+ if (mPaddingRight > 0) {
+ mPaddingRight = Bitmap.scaleFromDensity(
+ mPaddingRight, sourceDensity, targetDensity);
+ }
+ if (mPaddingBottom > 0) {
+ mPaddingBottom = Bitmap.scaleFromDensity(
+ mPaddingBottom, sourceDensity, targetDensity);
+ }
+ if (mPaddingStart > 0) {
+ mPaddingStart = Bitmap.scaleFromDensity(
+ mPaddingStart, sourceDensity, targetDensity);
+ }
+ if (mPaddingEnd > 0) {
+ mPaddingEnd = Bitmap.scaleFromDensity(
+ mPaddingEnd, sourceDensity, targetDensity);
+ }
+ }
+
@Override
public boolean canApplyTheme() {
if (mThemeAttrs != null || super.canApplyTheme()) {
@@ -1844,7 +1988,7 @@
}
@Override
- public Drawable newDrawable(Resources res) {
+ public Drawable newDrawable(@Nullable Resources res) {
return new LayerDrawable(this, res);
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 2690223..a196fad 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -409,18 +409,20 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
// Force padding default to STACK before inflating.
setPaddingMode(PADDING_MODE_STACK);
+ // Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
- setTargetDensity(r.getDisplayMetrics());
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ a.recycle();
updateLocalState();
}
@@ -461,7 +463,7 @@
/**
* Initializes the constant state from the values in the typed array.
*/
- private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+ private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
final RippleState state = mState;
// Account for any configuration changes.
@@ -477,11 +479,9 @@
mState.mMaxRadius = a.getDimensionPixelSize(
R.styleable.RippleDrawable_radius, mState.mMaxRadius);
-
- verifyRequiredAttributes(a);
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
if (mState.mColor == null && (mState.mTouchThemeAttrs == null
|| mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) {
throw new XmlPullParserException(a.getPositionDescription() +
@@ -489,20 +489,8 @@
}
}
- /**
- * Set the density at which this drawable will be rendered.
- *
- * @param metrics The display metrics for this drawable.
- */
- private void setTargetDensity(DisplayMetrics metrics) {
- if (mDensity != metrics.density) {
- mDensity = metrics.density;
- invalidateSelf(false);
- }
- }
-
@Override
- public void applyTheme(Theme t) {
+ public void applyTheme(@NonNull Theme t) {
super.applyTheme(t);
final RippleState state = mState;
@@ -515,6 +503,7 @@
R.styleable.RippleDrawable);
try {
updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
} catch (XmlPullParserException e) {
throw new RuntimeException(e);
} finally {
@@ -555,7 +544,8 @@
mBackground = new RippleBackground(this, mHotspotBounds, mForceSoftware);
}
- mBackground.setup(mState.mMaxRadius, mDensity);
+ final float densityScale = mState.mDensity * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ mBackground.setup(mState.mMaxRadius, densityScale);
mBackground.enter(focused);
}
@@ -1002,6 +992,23 @@
mTouchThemeAttrs = origs.mTouchThemeAttrs;
mColor = origs.mColor;
mMaxRadius = origs.mMaxRadius;
+
+ if (origs.mDensity != mDensity) {
+ applyDensityScaling(orig.mDensity, mDensity);
+ }
+ }
+ }
+
+ @Override
+ protected void onDensityChanged(int sourceDensity, int targetDensity) {
+ super.onDensityChanged(sourceDensity, targetDensity);
+
+ applyDensityScaling(sourceDensity, targetDensity);
+ }
+
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ if (mMaxRadius != RADIUS_AUTO) {
+ mMaxRadius = Bitmap.scaleFromDensity(mMaxRadius, sourceDensity, targetDensity);
}
}
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 036a078..1531ba2 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -21,6 +21,8 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.content.res.Resources;
@@ -58,22 +60,46 @@
* Creates a new rotating drawable with no wrapped drawable.
*/
public RotateDrawable() {
- this(new RotateState(null), null);
+ this(new RotateState(null, null), null);
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
- super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible);
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
+ super.inflate(r, parser, attrs, theme);
updateStateFromTypedArray(a);
- inflateChildDrawable(r, parser, attrs, theme);
verifyRequiredAttributes(a);
a.recycle();
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ final RotateState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
+ try {
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
+ }
+ }
+ }
+
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
if (getDrawable() == null && (mState.mThemeAttrs == null
|| mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) {
@@ -83,11 +109,14 @@
}
}
- @Override
- void updateStateFromTypedArray(TypedArray a) {
- super.updateStateFromTypedArray(a);
-
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
final RotateState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
@@ -109,35 +138,6 @@
state.mToDegrees = a.getFloat(
R.styleable.RotateDrawable_toDegrees, state.mToDegrees);
state.mCurrentDegrees = state.mFromDegrees;
-
- final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable);
- if (dr != null) {
- setDrawable(dr);
- }
- }
-
- @Override
- public void applyTheme(Theme t) {
- final RotateState state = mState;
- if (state == null) {
- return;
- }
-
- if (state.mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
- try {
- updateStateFromTypedArray(a);
- verifyRequiredAttributes(a);
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } finally {
- a.recycle();
- }
- }
-
- // The drawable may have changed as a result of applying the theme, so
- // apply the theme to the wrapped drawable last.
- super.applyTheme(t);
}
@Override
@@ -316,11 +316,13 @@
@Override
DrawableWrapperState mutateConstantState() {
- mState = new RotateState(mState);
+ mState = new RotateState(mState, null);
return mState;
}
static final class RotateState extends DrawableWrapper.DrawableWrapperState {
+ private int[] mThemeAttrs;
+
boolean mPivotXRel = true;
float mPivotX = 0.5f;
boolean mPivotYRel = true;
@@ -329,8 +331,8 @@
float mToDegrees = 360.0f;
float mCurrentDegrees = 0.0f;
- RotateState(RotateState orig) {
- super(orig);
+ RotateState(RotateState orig, Resources res) {
+ super(orig, res);
if (orig != null) {
mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index f9206b7..f87c19a 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -21,6 +21,8 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
@@ -67,7 +69,7 @@
private ScaleState mState;
ScaleDrawable() {
- this(new ScaleState(null), null);
+ this(new ScaleState(null, null), null);
}
/**
@@ -83,7 +85,7 @@
* is at the maximum value, or -1 to not scale height
*/
public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
- this(new ScaleState(null), null);
+ this(new ScaleState(null, null), null);
mState.mGravity = gravity;
mState.mScaleWidth = scaleWidth;
@@ -93,20 +95,46 @@
}
@Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
+
+ // Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
updateStateFromTypedArray(a);
- inflateChildDrawable(r, parser, attrs, theme);
verifyRequiredAttributes(a);
a.recycle();
updateLocalState();
}
- private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ final ScaleState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
+ try {
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ updateLocalState();
+ }
+
+ private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
if (getDrawable() == null && (mState.mThemeAttrs == null
|| mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
@@ -116,11 +144,18 @@
}
}
- @Override
- void updateStateFromTypedArray(TypedArray a) {
- super.updateStateFromTypedArray(a);
-
+ private void updateStateFromTypedArray(@NonNull TypedArray a) {
final ScaleState state = mState;
+ if (state == null) {
+ return;
+ }
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
state.mScaleWidth = getPercent(a,
R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
state.mScaleHeight = getPercent(a,
@@ -131,11 +166,6 @@
R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
state.mInitialLevel = a.getInt(
R.styleable.ScaleDrawable_level, state.mInitialLevel);
-
- final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable);
- if (dr != null) {
- setDrawable(dr);
- }
}
private static float getPercent(TypedArray a, int index, float defaultValue) {
@@ -157,33 +187,6 @@
}
@Override
- public void applyTheme(Theme t) {
- final ScaleState state = mState;
- if (state == null) {
- return;
- }
-
- if (state.mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(
- state.mThemeAttrs, R.styleable.ScaleDrawable);
- try {
- updateStateFromTypedArray(a);
- verifyRequiredAttributes(a);
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } finally {
- a.recycle();
- }
- }
-
- // The drawable may have changed as a result of applying the theme, so
- // apply the theme to the wrapped drawable last.
- super.applyTheme(t);
-
- updateLocalState();
- }
-
- @Override
public void draw(Canvas canvas) {
final Drawable d = getDrawable();
if (d != null && d.getLevel() != 0) {
@@ -243,7 +246,7 @@
@Override
DrawableWrapperState mutateConstantState() {
- mState = new ScaleState(mState);
+ mState = new ScaleState(mState, null);
return mState;
}
@@ -251,14 +254,16 @@
/** Constant used to disable scaling for a particular dimension. */
private static final float DO_NOT_SCALE = -1.0f;
+ private int[] mThemeAttrs;
+
float mScaleWidth = DO_NOT_SCALE;
float mScaleHeight = DO_NOT_SCALE;
int mGravity = Gravity.LEFT;
boolean mUseIntrinsicSizeAsMin = false;
int mInitialLevel = 0;
- ScaleState(ScaleState orig) {
- super(orig);
+ ScaleState(ScaleState orig, Resources res) {
+ super(orig, res);
if (orig != null) {
mScaleWidth = orig.mScaleWidth;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4385e70..d94c91d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -113,6 +113,7 @@
endef
hwui_c_includes += \
+ external/skia/include/private \
external/skia/src/core
hwui_shared_libraries := \
@@ -202,6 +203,7 @@
unit_tests/CanvasStateTests.cpp \
unit_tests/ClipAreaTests.cpp \
unit_tests/DamageAccumulatorTests.cpp \
+ unit_tests/FatVectorTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/StringUtilsTests.cpp
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 94806ca..0868853 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -25,112 +25,210 @@
namespace android {
namespace uirenderer {
-Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
- Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
- if (!texture) {
- return caches.textureCache.get(bitmap);
- }
- return texture;
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+ uint32_t viewportWidth, uint32_t viewportHeight)
+ : texture(caches)
+ , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
+ texture.width = textureWidth;
+ texture.height = textureHeight;
+
+ caches.textureState().activateTexture(0);
+ glGenTextures(1, &texture.id);
+ caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+ texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+ // not setting filter on texture, since it's set when drawing, based on transform
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
-void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
- bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
- renderState.scissor().setEnabled(useScissor);
- if (useScissor) {
- const Rect& clip = state.computedState.clipRect;
- renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
- clip.getWidth(), clip.getHeight());
- }
- renderState.render(glop, orthoMatrix);
- didDraw = true;
+////////////////////////////////////////////////////////////////////////////////
+// BakedOpRenderer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
+ // TODO: really should be caching these!
+ OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
+ mRenderTarget.offscreenBuffer = buffer;
+
+ // create and bind framebuffer
+ mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
+ mRenderState.bindFramebuffer(mRenderTarget.frameBufferId);
+
+ // attach the texture to the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ buffer->texture.id, 0);
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
+ LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
+ "framebuffer incomplete!");
+
+ // Clear the FBO
+ mRenderState.scissor().setEnabled(false);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Change the viewport & ortho projection
+ setViewport(width, height);
+ return buffer;
}
-void BakedOpRenderer::startFrame(Info& info) {
- info.renderState.setViewport(info.viewportWidth, info.viewportHeight);
- info.renderState.blend().syncEnabled();
- Caches::getInstance().clearGarbage();
+void BakedOpRenderer::endLayer() {
+ mRenderTarget.offscreenBuffer = nullptr;
- if (!info.opaque) {
+ // Detach the texture from the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
+ mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
+ mRenderTarget.frameBufferId = -1;
+}
+
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+ mRenderState.bindFramebuffer(0);
+ setViewport(width, height);
+ mCaches.clearGarbage();
+
+ if (!mOpaque) {
// TODO: partial invalidate!
- info.renderState.scissor().setEnabled(false);
+ mRenderState.scissor().setEnabled(false);
glClear(GL_COLOR_BUFFER_BIT);
- info.didDraw = true;
+ mHasDrawn = true;
}
}
-void BakedOpRenderer::endFrame(Info& info) {
- info.caches.pathCache.trim();
- info.caches.tessellationCache.trim();
+
+void BakedOpRenderer::endFrame() {
+ mCaches.pathCache.trim();
+ mCaches.tessellationCache.trim();
#if DEBUG_OPENGL
GLUtils::dumpGLErrors();
#endif
#if DEBUG_MEMORY_USAGE
- info.caches.dumpMemoryUsage();
+ mCaches.dumpMemoryUsage();
#else
if (Properties::debugLevel & kDebugMemory) {
- info.caches.dumpMemoryUsage();
+ mCaches.dumpMemoryUsage();
}
#endif
}
-void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
+void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) {
+ mRenderTarget.viewportWidth = width;
+ mRenderTarget.viewportHeight = height;
+ mRenderTarget.orthoMatrix.loadOrtho(width, height);
+
+ mRenderState.setViewport(width, height);
+ mRenderState.blend().syncEnabled();
+}
+
+Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ if (!texture) {
+ return mCaches.textureCache.get(bitmap);
+ }
+ return texture;
+}
+
+void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
+ bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
+ mRenderState.scissor().setEnabled(useScissor);
+ if (useScissor) {
+ const Rect& clip = state.computedState.clipRect;
+ mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
+ clip.getWidth(), clip.getHeight());
+ }
+ mRenderState.render(glop, mRenderTarget.orthoMatrix);
+ mHasDrawn = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// static BakedOpDispatcher methods
+////////////////////////////////////////////////////////////////////////////////
+
+void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
- info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
- Texture* texture = info.getTexture(op.bitmap);
+void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+ Texture* texture = renderer.getTexture(op.bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
Glop glop;
- GlopBuilder(info.renderState, info.caches, &glop)
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
.build();
- info.renderGlop(state, glop);
+ renderer.renderGlop(state, glop);
}
-void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
Glop glop;
- GlopBuilder(info.renderState, info.caches, &glop)
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshUnitQuad()
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRect(op.unmappedBounds)
.build();
- info.renderGlop(state, glop);
+ renderer.renderGlop(state, glop);
}
-void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
Glop glop;
- GlopBuilder(info.renderState, info.caches, &glop)
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
.build();
- info.renderGlop(state, glop);
+ renderer.renderGlop(state, glop);
}
-void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
+void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+ OffscreenBuffer* buffer = *op.layerHandle;
+
+ // TODO: extend this to handle HW layers & paint properties which
+ // reside in node.properties().layerProperties()
+ float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
+ const bool tryToSnap = state.computedState.transform.isPureTranslate();
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
+ .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+
+ // destroy and delete, since each clipped saveLayer is only drawn once.
+ buffer->texture.deleteTexture();
+
+ // TODO: return texture/offscreenbuffer to cache!
+ delete buffer;
}
} // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index f45dbe4..16afad4 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -25,48 +25,85 @@
class Caches;
struct Glop;
+class Layer;
class RenderState;
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ */
+class OffscreenBuffer {
+public:
+ OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+ uint32_t viewportWidth, uint32_t viewportHeight);
+
+ Texture texture;
+ Rect texCoords;
+ Region region;
+};
+
+/**
+ * Main rendering manager for a collection of work - one frame + any contained FBOs.
+ *
+ * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
+ * place where FBOs are bound, created, and destroyed.
+ *
+ * All rendering operations will be sent by the Dispatcher, a collection of static methods,
+ * which has intentionally limited access to the renderer functionality.
+ */
class BakedOpRenderer {
public:
- class Info {
- public:
- Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque)
- : renderState(renderState)
- , caches(caches)
- , opaque(opaque)
- , viewportWidth(viewportWidth)
- , viewportHeight(viewportHeight) {
- orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
- }
+ BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+ : mRenderState(renderState)
+ , mCaches(caches)
+ , mOpaque(opaque) {
+ }
- Texture* getTexture(const SkBitmap* bitmap);
+ RenderState& renderState() { return mRenderState; }
+ Caches& caches() { return mCaches; }
- void renderGlop(const BakedOpState& state, const Glop& glop);
- RenderState& renderState;
- Caches& caches;
+ void startFrame(uint32_t width, uint32_t height);
+ void endFrame();
+ OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
+ void endLayer();
- bool didDraw = false;
- bool opaque;
+ Texture* getTexture(const SkBitmap* bitmap);
+ void renderGlop(const BakedOpState& state, const Glop& glop);
+ bool didDraw() { return mHasDrawn; }
+private:
+ void setViewport(uint32_t width, uint32_t height);
- // where should these live? layer state object?
- int viewportWidth;
- int viewportHeight;
+ RenderState& mRenderState;
+ Caches& mCaches;
+ bool mOpaque;
+ bool mHasDrawn = false;
+
+ // render target state - setup by start/end layer/frame
+ // only valid to use in between start/end pairs.
+ struct {
+ GLuint frameBufferId = 0;
+ OffscreenBuffer* offscreenBuffer = nullptr;
+ uint32_t viewportWidth = 0;
+ uint32_t viewportHeight = 0;
Matrix4 orthoMatrix;
- };
+ } mRenderTarget;
+};
- static void startFrame(Info& info);
- static void endFrame(Info& info);
-
- /**
- * Declare all "onBitmapOp(...)" style function for every op type.
- *
- * These functions will perform the actual rendering of the individual operations in OpenGL,
- * given the transform/clip and other state built into the BakedOpState object passed in.
- */
- #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
- MAP_OPS(BAKED_OP_RENDERER_METHOD);
+/**
+ * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
+ * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
+ *
+ * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
+ * minimal through public BakedOpRenderer APIs.
+ */
+class BakedOpDispatcher {
+public:
+ // Declares all "onBitmapOp(...)" style methods for every op type
+#define DISPATCH_METHOD(Type) \
+ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
+ MAP_OPS(DISPATCH_METHOD);
};
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index bad3972..f5e5735 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -584,7 +584,7 @@
// it to the bitmap pile
SkBitmap bitmap;
SkShader::TileMode xy[2];
- if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+ if (shader->isABitmap(&bitmap, nullptr, xy)) {
refBitmap(bitmap);
return;
}
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index f99d92b..489ebc1 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -155,8 +155,7 @@
if (fbo) {
if (flush) LayerRenderer::flushLayer(renderState, this);
- // If put fails the cache will delete the FBO
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
fbo = 0;
}
}
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 227271d..e9e5d81 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -189,7 +189,7 @@
LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
Caches& caches = Caches::getInstance();
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.genFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return nullptr;
@@ -204,7 +204,7 @@
// We first obtain a layer before comparing against the max texture size
// because layers are not allocated at the exact desired size. They are
- // always created slighly larger to improve recycling
+ // always created slightly larger to improve recycling
const uint32_t maxTextureSize = caches.maxTextureSize;
if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) {
ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -357,7 +357,7 @@
&& bitmap->width() <= caches.maxTextureSize
&& bitmap->height() <= caches.maxTextureSize) {
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.getFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return false;
@@ -465,7 +465,7 @@
layer->setAlpha(alpha, mode);
layer->setFbo(previousLayerFbo);
caches.textureState().deleteTexture(texture);
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
renderState.setViewport(previousViewportWidth, previousViewportHeight);
return status;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index c1417c4..ddeb336 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -203,7 +203,7 @@
};
// iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still interate to find similar batch to insert after
+// if no target, merging ops still iterate to find similar batch to insert after
void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
BatchBase** targetBatch, size_t* insertBatchIndex) const {
for (int i = mBatches.size() - 1; i >= 0; i--) {
@@ -277,7 +277,8 @@
}
}
-void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const {
+ ATRACE_NAME("flush drawing commands");
for (const BatchBase* batch : mBatches) {
// TODO: different behavior based on batch->isMerging()
for (const BakedOpState* op : batch->getOps()) {
@@ -292,18 +293,14 @@
}
}
-OpReorderer::OpReorderer()
+OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+ const std::vector< sp<RenderNode> >& nodes)
: mCanvasState(*this) {
- mLayerReorderers.emplace_back();
+ ATRACE_NAME("prepare drawing commands");
+
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
mLayerStack.push_back(0);
-}
-void OpReorderer::onViewportInitialized() {}
-
-void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-
-void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
- const std::vector< sp<RenderNode> >& nodes) {
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Vector3());
@@ -321,13 +318,22 @@
}
}
-void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) {
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+ : mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
+
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+ mLayerStack.push_back(0);
+
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
0, 0, viewportWidth, viewportHeight, Vector3());
deferImpl(displayList);
}
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
@@ -348,15 +354,6 @@
}
}
-void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
- ATRACE_NAME("flush drawing commands");
- // Relay through layers in reverse order, since layers
- // later in the list will be drawn by earlier ones
- for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
- mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
- }
-}
-
void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) {
return;
@@ -405,15 +402,17 @@
// TODO: test rejection at defer time, where the bounds become empty
void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+ const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+ const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
mCanvasState.writableSnapshot()->transform->loadIdentity();
- mCanvasState.writableSnapshot()->initializeViewport(
- (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+ mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
// create a new layer, and push its index on the stack
mLayerStack.push_back(mLayerReorderers.size());
- mLayerReorderers.emplace_back();
+ mLayerReorderers.emplace_back(layerWidth, layerHeight);
mLayerReorderers.back().beginLayerOp = &op;
}
@@ -432,7 +431,8 @@
beginLayerOp.unmappedBounds,
beginLayerOp.localMatrix,
beginLayerOp.localClipRect,
- beginLayerOp.paint);
+ beginLayerOp.paint,
+ &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
if (bakedOpState) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 73dc9af..927ecfa 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -33,6 +33,7 @@
class BakedOpState;
class BatchBase;
class MergingOpBatch;
+class OffscreenBuffer;
class OpBatch;
class Rect;
@@ -55,7 +56,7 @@
}
class OpReorderer : public CanvasStateClient {
- typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
+ typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher;
/**
* Stores the deferred render operations and state used to compute ordering
@@ -63,6 +64,10 @@
*/
class LayerReorderer {
public:
+ LayerReorderer(uint32_t width, uint32_t height)
+ : width(width)
+ , height(height) {}
+
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
void locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -75,7 +80,11 @@
void deferMergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
- void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+ void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const;
+
+ bool empty() const {
+ return mBatches.empty();
+ }
void clear() {
mBatches.clear();
@@ -83,9 +92,12 @@
void dump() const;
+ OffscreenBuffer* offscreenBuffer = nullptr;
const BeginLayerOp* beginLayerOp = nullptr;
-
+ const uint32_t width;
+ const uint32_t height;
private:
+
std::vector<BatchBase*> mBatches;
/**
@@ -100,14 +112,13 @@
};
public:
- OpReorderer();
- virtual ~OpReorderer() {}
-
// TODO: not final, just presented this way for simplicity. Layers too?
- void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
+ OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes);
- void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+ OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+
+ virtual ~OpReorderer() {}
/**
* replayBakedOps() is templated based on what class will receive ops being replayed.
@@ -117,20 +128,33 @@
*
* For example a BitmapOp would resolve, via the lambda lookup, to calling:
*
- * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
+ * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
*/
#define BAKED_OP_RECEIVER(Type) \
- [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
- StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
+ [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \
+ StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \
},
- template <typename StaticReceiver, typename Arg>
- void replayBakedOps(Arg& arg) {
- static BakedOpReceiver receivers[] = {
+ template <typename StaticDispatcher, typename Renderer>
+ void replayBakedOps(Renderer& renderer) {
+ static BakedOpDispatcher receivers[] = {
MAP_OPS(BAKED_OP_RECEIVER)
};
- StaticReceiver::startFrame(arg);
- replayBakedOpsImpl((void*)&arg, receivers);
- StaticReceiver::endFrame(arg);
+
+ // Relay through layers in reverse order, since layers
+ // later in the list will be drawn by earlier ones
+ for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
+ LayerReorderer& layer = mLayerReorderers[i];
+ if (!layer.empty()) {
+ layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
+ layer.replayBakedOpsImpl((void*)&renderer, receivers);
+ renderer.endLayer();
+ }
+ }
+
+ const LayerReorderer& fbo0 = mLayerReorderers[0];
+ renderer.startFrame(fbo0.width, fbo0.height);
+ fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
+ renderer.endFrame();
}
void dump() const {
@@ -155,7 +179,7 @@
void deferImpl(const DisplayList& displayList);
- void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
+ void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
/**
* Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d4f65b6..8c3603b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -673,7 +673,7 @@
bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
layer->clipRect.set(clip);
- layer->setFbo(mCaches.fboCache.get());
+ layer->setFbo(mRenderState.genFramebuffer());
writableSnapshot()->region = &writableSnapshot()->layer->region;
writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index dd01637..7874d85 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -29,6 +29,7 @@
namespace android {
namespace uirenderer {
+class OffscreenBuffer;
class RenderNode;
struct Vertex;
@@ -136,8 +137,12 @@
};
struct LayerOp : RecordedOp {
- LayerOp(BASE_PARAMS)
- : SUPER(LayerOp) {}
+ LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
+ : SUPER(LayerOp)
+ , layerHandle(layerHandle) {}
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
};
}; // namespace uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 1f113bc..273af3a 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -458,7 +458,7 @@
// it to the bitmap pile
SkBitmap bitmap;
SkShader::TileMode xy[2];
- if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+ if (shader->isABitmap(&bitmap, nullptr, xy)) {
refBitmap(bitmap);
return;
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 9c32b1a..454ee24 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -27,6 +27,8 @@
#include "Snapshot.h"
#include "SkDrawFilter.h"
+#include "SkPaint.h"
+#include "SkTLazy.h"
#include <vector>
namespace android {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 351fbaa..39cb8e9 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -16,13 +16,6 @@
#include "RenderNode.h"
-#include <algorithm>
-#include <string>
-
-#include <SkCanvas.h>
-#include <algorithm>
-
-
#include "DamageAccumulator.h"
#include "Debug.h"
#if HWUI_NEW_OPS
@@ -39,6 +32,12 @@
#include "protos/hwui.pb.h"
#include "protos/ProtoHelpers.h"
+#include <SkCanvas.h>
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
namespace android {
namespace uirenderer {
@@ -79,6 +78,11 @@
mNeedsDisplayListSync = true;
delete mStagingDisplayList;
mStagingDisplayList = displayList;
+ // If mParentCount == 0 we are the sole reference to this RenderNode,
+ // so immediately free the old display list
+ if (!mParentCount && !mStagingDisplayList) {
+ deleteDisplayList();
+ }
}
/**
@@ -250,7 +254,8 @@
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
+ mLayer = LayerRenderer::createRenderLayer(
+ info.canvasContext.getRenderState(), getWidth(), getHeight());
applyLayerPropertiesToLayer(info);
damageSelf(info);
transformUpdateNeeded = true;
@@ -269,9 +274,16 @@
if (!mLayer) {
Caches::getInstance().dumpMemoryUsage();
if (info.errorHandler) {
- std::string msg = "Unable to create layer for ";
- msg += getName();
- info.errorHandler->onError(msg);
+ std::ostringstream err;
+ err << "Unable to create layer for " << getName();
+ const int maxTextureSize = Caches::getInstance().maxTextureSize;
+ if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
+ err << ", size " << getWidth() << "x" << getHeight()
+ << " exceeds max size " << maxTextureSize;
+ } else {
+ err << ", see logcat for more info";
+ }
+ info.errorHandler->onError(err.str());
}
return;
}
@@ -293,12 +305,10 @@
info.renderer->pushLayerUpdate(mLayer);
}
- if (info.canvasContext) {
- // There might be prefetched layers that need to be accounted for.
- // That might be us, so tell CanvasContext that this layer is in the
- // tree and should not be destroyed.
- info.canvasContext->markLayerInUse(this);
- }
+ // There might be prefetched layers that need to be accounted for.
+ // That might be us, so tell CanvasContext that this layer is in the
+ // tree and should not be destroyed.
+ info.canvasContext.markLayerInUse(this);
}
/**
@@ -419,7 +429,8 @@
TextureCache& cache = Caches::getInstance().textureCache;
info.out.hasFunctors |= subtree->getFunctors().size();
for (auto&& bitmapResource : subtree->getBitmapResources()) {
- info.prepareTextures = cache.prefetchAndMarkInUse(info.canvasContext, bitmapResource);
+ void* ownerToken = &info.canvasContext;
+ info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource);
}
for (auto&& op : subtree->getChildren()) {
RenderNode* childNode = op->renderNode;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 36633b5..a8f8134 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -194,15 +194,13 @@
void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
SkCanvas* newCanvas = new SkCanvas(bitmap);
- SkASSERT(newCanvas);
if (!bitmap.isNull()) {
// Copy the canvas matrix & clip state.
newCanvas->setMatrix(mCanvas->getTotalMatrix());
- if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) {
- ClipCopier copier(newCanvas);
- mCanvas->replayClips(&copier);
- }
+
+ ClipCopier copier(newCanvas);
+ mCanvas->replayClips(&copier);
}
// unrefs the existing canvas
@@ -217,15 +215,15 @@
// ----------------------------------------------------------------------------
bool SkiaCanvas::isOpaque() {
- return mCanvas->getDevice()->accessBitmap(false).isOpaque();
+ return mCanvas->imageInfo().isOpaque();
}
int SkiaCanvas::width() {
- return mCanvas->getBaseLayerSize().width();
+ return mCanvas->imageInfo().width();
}
int SkiaCanvas::height() {
- return mCanvas->getBaseLayerSize().height();
+ return mCanvas->imageInfo().height();
}
// ----------------------------------------------------------------------------
@@ -581,7 +579,7 @@
float dstRight, float dstBottom, const SkPaint* paint) {
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint);
+ mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint);
}
void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index c3f5eb2..2d5f70f 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -125,7 +125,7 @@
}
void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr,
- const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) {
+ const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height());
// TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 0de9650..2fe4327 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -63,7 +63,7 @@
virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
const SkPaint*) override;
virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst,
- const SkPaint* paint, DrawBitmapRectFlags flags) override;
+ const SkPaint* paint, SrcRectConstraint) override;
virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
const SkRect& dst, const SkPaint*) override;
virtual void onDrawSprite(const SkBitmap&, int left, int top,
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 6c105cf..83652c6 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -204,7 +204,7 @@
SkiaShaderData::BitmapShaderData* outData) {
SkBitmap bitmap;
SkShader::TileMode xy[2];
- if (shader.asABitmap(&bitmap, nullptr, xy) != SkShader::kDefault_BitmapType) {
+ if (!shader.isABitmap(&bitmap, nullptr, xy)) {
return false;
}
@@ -272,7 +272,7 @@
}
// The shader is not a gradient. Check for a bitmap shader.
- if (shader.asABitmap(nullptr, nullptr, nullptr) == SkShader::kDefault_BitmapType) {
+ if (shader.isABitmap()) {
return kBitmap_SkiaShaderType;
}
return kNone_SkiaShaderType;
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 98e6146..1c31487 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -55,70 +55,46 @@
MODE_RT_ONLY,
};
- explicit TreeInfo(TraversalMode mode, RenderState& renderState)
- : mode(mode)
- , prepareTextures(mode == MODE_FULL)
- , runAnimations(true)
- , damageAccumulator(nullptr)
- , renderState(renderState)
- , renderer(nullptr)
- , errorHandler(nullptr)
- , canvasContext(nullptr)
- {}
-
- explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
- : mode(mode)
- , prepareTextures(mode == MODE_FULL)
- , runAnimations(clone.runAnimations)
- , damageAccumulator(clone.damageAccumulator)
- , renderState(clone.renderState)
- , renderer(clone.renderer)
- , errorHandler(clone.errorHandler)
- , canvasContext(clone.canvasContext)
+ TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
+ : mode(mode)
+ , prepareTextures(mode == MODE_FULL)
+ , canvasContext(canvasContext)
{}
TraversalMode mode;
// TODO: Remove this? Currently this is used to signal to stop preparing
// textures if we run out of cache space.
bool prepareTextures;
+ renderthread::CanvasContext& canvasContext;
// TODO: buildLayer uses this to suppress running any animations, but this
// should probably be refactored somehow. The reason this is done is
// because buildLayer is not setup for injecting the animationHook, as well
// as this being otherwise wasted work as all the animators will be
// re-evaluated when the frame is actually drawn
- bool runAnimations;
+ bool runAnimations = true;
// Must not be null during actual usage
- DamageAccumulator* damageAccumulator;
- RenderState& renderState;
+ DamageAccumulator* damageAccumulator = nullptr;
// The renderer that will be drawing the next frame. Use this to push any
// layer updates or similar. May be NULL.
- OpenGLRenderer* renderer;
- ErrorHandler* errorHandler;
- // May be NULL (TODO: can it really?)
- renderthread::CanvasContext* canvasContext;
+ OpenGLRenderer* renderer = nullptr;
+ ErrorHandler* errorHandler = nullptr;
struct Out {
- Out()
- : hasFunctors(false)
- , hasAnimations(false)
- , requiresUiRedraw(false)
- , canDrawThisFrame(true)
- {}
- bool hasFunctors;
+ bool hasFunctors = false;
// This is only updated if evaluateAnimations is true
- bool hasAnimations;
+ bool hasAnimations = false;
// This is set to true if there is an animation that RenderThread cannot
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
- bool requiresUiRedraw;
+ bool requiresUiRedraw = false;
// This is set to true if draw() can be called this frame
// false means that we must delay until the next vsync pulse as frame
// production is outrunning consumption
// NOTE that if this is false CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
- bool canDrawThisFrame;
+ bool canDrawThisFrame = true;
} out;
// TODO: Damage calculations
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index cf96d44..43f170f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,8 +48,7 @@
void BM_OpReorderer_defer::Run(int iters) {
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer;
- reorderer.defer(200, 200, *sReorderingDisplayList);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -60,12 +59,11 @@
TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer;
- reorderer.defer(200, 200, *sReorderingDisplayList);
- MicroBench::DoNotOptimize(&reorderer);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList);
- BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
- reorderer.replayBakedOps<BakedOpRenderer>(info);
+ BakedOpRenderer renderer(caches, renderState, true);
+ reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
+ MicroBench::DoNotOptimize(&renderer);
}
StopBenchmarkTiming();
});
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index dfa70ac..9637117 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -125,6 +125,21 @@
}
}
+GLuint RenderState::genFramebuffer() {
+ GLuint ret;
+ glGenFramebuffers(1, &ret);
+ return ret;
+}
+
+void RenderState::deleteFramebuffer(GLuint fbo) {
+ if (mFramebuffer == fbo) {
+ // GL defines that deleting the currently bound FBO rebinds FBO 0.
+ // Reflect this in our cached value.
+ mFramebuffer = 0;
+ }
+ glDeleteFramebuffers(1, &fbo);
+}
+
void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
if (mode == DrawGlInfo::kModeProcessNoContext) {
// If there's no context we don't need to interrupt as there's
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9ae0845..3cda170 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,24 +16,25 @@
#ifndef RENDERSTATE_H
#define RENDERSTATE_H
-#include <set>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <utils/Mutex.h>
-#include <utils/Functor.h>
-#include <utils/RefBase.h>
-#include <private/hwui/DrawGlInfo.h>
-#include <renderstate/Blend.h>
-
#include "AssetAtlas.h"
#include "Caches.h"
#include "Glop.h"
+#include "renderstate/Blend.h"
#include "renderstate/MeshState.h"
#include "renderstate/PixelBufferState.h"
#include "renderstate/Scissor.h"
#include "renderstate/Stencil.h"
#include "utils/Macros.h"
+#include <set>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <ui/Region.h>
+#include <utils/Mutex.h>
+#include <utils/Functor.h>
+#include <utils/RefBase.h>
+#include <private/hwui/DrawGlInfo.h>
+
namespace android {
namespace uirenderer {
@@ -49,6 +50,8 @@
// wrapper of Caches for users to migrate to.
class RenderState {
PREVENT_COPY_AND_ASSIGN(RenderState);
+ friend class renderthread::RenderThread;
+ friend class Caches;
public:
void onGLContextCreated();
void onGLContextDestroyed();
@@ -57,7 +60,9 @@
void getViewport(GLsizei* outWidth, GLsizei* outHeight);
void bindFramebuffer(GLuint fbo);
- GLint getFramebuffer() { return mFramebuffer; }
+ GLuint getFramebuffer() { return mFramebuffer; }
+ GLuint genFramebuffer();
+ void deleteFramebuffer(GLuint fbo);
void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
@@ -93,10 +98,8 @@
Stencil& stencil() { return *mStencil; }
void dump();
-private:
- friend class renderthread::RenderThread;
- friend class Caches;
+private:
void interruptForFunctorInvoke();
void resumeFromFunctorInvoke();
void assertOnGLThread();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 83824fd..c1f6670 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -27,6 +27,7 @@
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
+#include "utils/TimeUtils.h"
#if HWUI_NEW_OPS
#include "BakedOpRenderer.h"
@@ -108,6 +109,7 @@
const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
mHaveNewSurface = true;
+ mSwapHistory.clear();
makeCurrent();
} else {
mRenderThread.removeFrameCallback(this);
@@ -197,7 +199,6 @@
info.damageAccumulator = &mDamageAccumulator;
info.renderer = mCanvas;
- info.canvasContext = this;
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -217,13 +218,30 @@
return;
}
- int runningBehind = 0;
- // TODO: This query is moderately expensive, investigate adding some sort
- // of fast-path based off when we last called eglSwapBuffers() as well as
- // last vsync time. Or something.
- mNativeWindow->query(mNativeWindow.get(),
- NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
- info.out.canDrawThisFrame = !runningBehind;
+ if (CC_LIKELY(mSwapHistory.size())) {
+ nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
+ const SwapHistory& lastSwap = mSwapHistory.back();
+ int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+ // The slight fudge-factor is to deal with cases where
+ // the vsync was estimated due to being slow handling the signal.
+ // See the logic in TimeLord#computeFrameTimeNanos or in
+ // Choreographer.java for details on when this happens
+ if (vsyncDelta < 2_ms) {
+ // Already drew for this vsync pulse, UI draw request missed
+ // the deadline for RT animations
+ info.out.canDrawThisFrame = false;
+ } else if (lastSwap.swapTime < latestVsync) {
+ info.out.canDrawThisFrame = true;
+ } else {
+ // We're maybe behind? Find out for sure
+ int runningBehind = 0;
+ mNativeWindow->query(mNativeWindow.get(),
+ NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+ info.out.canDrawThisFrame = !runningBehind;
+ }
+ } else {
+ info.out.canDrawThisFrame = true;
+ }
if (!info.out.canDrawThisFrame) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -266,11 +284,11 @@
Frame frame = mEglManager.beginFrame(mEglSurface);
- if (frame.width() != lastFrameWidth || frame.height() != lastFrameHeight) {
+ if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
// can't rely on prior content of window if viewport size changes
dirty.setEmpty();
- lastFrameWidth = frame.width();
- lastFrameHeight = frame.height();
+ mLastFrameWidth = frame.width();
+ mLastFrameHeight = frame.height();
} else if (mHaveNewSurface || frame.bufferAge() == 0) {
// New surface needs a full draw
dirty.setEmpty();
@@ -297,7 +315,7 @@
// last frame so there's nothing to union() against
// Therefore we only care about the > 1 case.
if (frame.bufferAge() > 1) {
- if (frame.bufferAge() > (int) mDamageHistory.size()) {
+ if (frame.bufferAge() > (int) mSwapHistory.size()) {
// We don't have enough history to handle this old of a buffer
// Just do a full-draw
dirty.set(0, 0, frame.width(), frame.height());
@@ -305,27 +323,22 @@
// At this point we haven't yet added the latest frame
// to the damage history (happens below)
// So we need to damage
- for (int i = mDamageHistory.size() - 1;
- i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) {
- dirty.join(mDamageHistory[i]);
+ for (int i = mSwapHistory.size() - 1;
+ i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
+ dirty.join(mSwapHistory[i].damage);
}
}
}
- // Add the screen damage to the ring buffer.
- mDamageHistory.next() = screenDirty;
-
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer;
- reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes);
- BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
- frame.width(), frame.height(), mOpaque);
+ OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+ BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
// TODO: profiler().draw(mCanvas);
- reorderer.replayBakedOps<BakedOpRenderer>(info);
+ reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
- bool drew = info.didDraw;
+ bool drew = renderer.didDraw();
#else
mCanvas->prepareDirty(frame.width(), frame.height(),
@@ -450,6 +463,10 @@
if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
setSurface(nullptr);
}
+ SwapHistory& swap = mSwapHistory.next();
+ swap.damage = screenDirty;
+ swap.swapTime = systemTime(CLOCK_MONOTONIC);
+ swap.vsyncTime = mRenderThread.timeLord().latestVsync();
mHaveNewSurface = false;
}
@@ -492,7 +509,7 @@
.setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
mRenderThread.timeLord().latestVsync());
- TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
if (info.out.canDrawThisFrame) {
draw();
@@ -536,7 +553,7 @@
// buildLayer() will leave the tree in an unknown state, so we must stop drawing
stopDrawing();
- TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
+ TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
info.renderer = mCanvas;
info.runAnimations = false;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 16956e6..30e6562 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -130,6 +130,10 @@
mContentDrawBounds.set(left, top, right, bottom);
}
+ RenderState& getRenderState() {
+ return mRenderThread.renderState();
+ }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -141,8 +145,8 @@
void freePrefetechedLayers();
- int lastFrameWidth = 0;
- int lastFrameHeight = 0;
+ EGLint mLastFrameWidth = 0;
+ EGLint mLastFrameHeight = 0;
RenderThread& mRenderThread;
EglManager& mEglManager;
@@ -150,7 +154,13 @@
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
- RingBuffer<SkRect, 3> mDamageHistory;
+ struct SwapHistory {
+ SkRect damage;
+ nsecs_t vsyncTime;
+ nsecs_t swapTime;
+ };
+
+ RingBuffer<SwapHistory, 3> mSwapHistory;
bool mOpaque;
OpenGLRenderer* mCanvas = nullptr;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index a47c9ec..ab860c7 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -87,7 +87,7 @@
bool canUnblockUiThread;
bool canDrawThisFrame;
{
- TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
+ TreeInfo info(TreeInfo::MODE_FULL, *mContext);
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 891af91..2eefd37 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -54,15 +54,10 @@
}
};
-static TestCanvas* startRecording(RenderNode* node) {
- TestCanvas* renderer = new TestCanvas(
- node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
- return renderer;
-}
-
-static void endRecording(TestCanvas* renderer, RenderNode* node) {
- node->setStagingDisplayList(renderer->finishRecording());
- delete renderer;
+static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
+ TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
+ contentCallback(canvas);
+ node.setStagingDisplayList(canvas.finishRecording());
}
class TreeContentAnimation {
@@ -75,7 +70,7 @@
frameCount = fc;
}
}
- virtual void createContent(int width, int height, TestCanvas* renderer) = 0;
+ virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
virtual void doFrame(int frameNr) = 0;
template <class T>
@@ -108,11 +103,9 @@
proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
- android::uirenderer::Rect DUMMY;
-
- TestCanvas* renderer = startRecording(rootNode);
- animation.createContent(width, height, renderer);
- endRecording(renderer, rootNode);
+ recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
+ animation.createContent(width, height, &canvas); //TODO: no&
+ });
// Do a few cold runs then reset the stats so that the caches are all hot
for (int i = 0; i < 3; i++) {
@@ -140,19 +133,19 @@
class ShadowGridAnimation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -171,9 +164,9 @@
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -187,19 +180,19 @@
class ShadowGrid2Animation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -218,9 +211,9 @@
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -233,15 +226,30 @@
class RectGridAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
- card = createCard(40, 40, 200, 200);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(false);
+ SkRegion region;
+ for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+ for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+ region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+ }
+ }
+
+ SkPaint paint;
+ paint.setColor(0xff00ffff);
+ canvas.drawRegion(region, paint);
+ });
+ canvas->drawRenderNode(card.get());
+
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -249,29 +257,6 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
- SkRegion region;
- for (int xOffset = 0; xOffset < width; xOffset+=2) {
- for (int yOffset = 0; yOffset < height; yOffset+=2) {
- region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
- }
- }
-
- SkPaint paint;
- paint.setColor(0xff00ffff);
- renderer->drawRegion(region, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
static Benchmark _RectGrid(BenchmarkInfo{
"rectgrid",
@@ -282,15 +267,22 @@
class OvalAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
- card = createCard(40, 40, 400, 400);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(0xFF000000);
+ canvas.drawOval(0, 0, 200, 200, paint);
+ });
+ canvas->drawRenderNode(card.get());
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
@@ -299,22 +291,6 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
-
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setColor(0xFF000000);
- renderer->drawOval(0, 0, width, height, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
static Benchmark _Oval(BenchmarkInfo{
"oval",
@@ -325,7 +301,7 @@
class PartialDamageTest : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
+ void createContent(int width, int height, TestCanvas* canvas) override {
static SkColor COLORS[] = {
0xFFF44336,
0xFF9C27B0,
@@ -333,13 +309,13 @@
0xFF4CAF50,
};
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
COLORS[static_cast<int>((y / dp(116))) % 4]);
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
@@ -350,10 +326,10 @@
cards[0]->mutateStagingProperties().setTranslationY(curFrame);
cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- TestCanvas* renderer = startRecording(cards[0].get());
- renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
- SkXfermode::kSrcOver_Mode);
- endRecording(renderer, cards[0].get());
+ recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+ canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
+ SkXfermode::kSrcOver_Mode);
+ });
}
static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
@@ -378,9 +354,9 @@
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [color](TestCanvas& canvas) {
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -393,16 +369,24 @@
});
-class SimpleRectGridAnimation : public TreeContentAnimation {
+class SaveLayerAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- SkPaint paint;
- paint.setColor(0xFF00FFFF);
- renderer->drawRect(0, 0, width, height, paint);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
- card = createCard(40, 40, 200, 200);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+ canvas.restore();
+ canvas.restore();
+ });
+
+ canvas->drawRenderNode(card.get());
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -410,24 +394,10 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
- SkPaint paint;
- paint.setColor(0xFFFF00FF);
- renderer->drawRect(0, 0, width, height, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
-static Benchmark _SimpleRectGrid(BenchmarkInfo{
- "simplerectgrid",
- "A simple collection of rects. "
- "Low CPU/GPU load.",
- TreeContentAnimation::run<SimpleRectGridAnimation>
+static Benchmark _SaveLayer(BenchmarkInfo{
+ "savelayer",
+ "A nested pair of clipped saveLayer operations. "
+ "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+ TreeContentAnimation::run<SaveLayerAnimation>
});
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 82aebea..bc1b69f 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -85,13 +85,5 @@
}
}
-#define UNSUPPORTED_OP(Info, Type) \
- static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
-
-class Info {
-public:
- int index = 0;
-};
-
}
}
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
new file mode 100644
index 0000000..fb760ac5
--- /dev/null
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <utils/FatVector.h>
+
+#include <unit_tests/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+template<class VectorType>
+static bool allocationIsInternal(VectorType& v) {
+ // allocation array (from &v[0] to &v[0] + v.capacity) is
+ // located within the vector object itself
+ return (char*)(&v) <= (char*)(&v[0])
+ && (char*)(&v + 1) >= (char*)(&v[0] + v.capacity());
+}
+
+TEST(FatVector, baseline) {
+ // Verify allocation behavior FatVector contrasts against - allocations are always external
+ std::vector<int> v;
+ for (int i = 0; i < 50; i++) {
+ v.push_back(i);
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+}
+
+TEST(FatVector, simpleAllocate) {
+ FatVector<int, 4> v;
+ EXPECT_EQ(4u, v.capacity());
+
+ // can insert 4 items into internal buffer
+ for (int i = 0; i < 4; i++) {
+ v.push_back(i);
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+
+ // then will fall back to external allocation
+ for (int i = 5; i < 50; i++) {
+ v.push_back(i);
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+}
+
+TEST(FatVector, shrink) {
+ FatVector<int, 10> v;
+ EXPECT_TRUE(allocationIsInternal(v));
+
+ // push into external alloc
+ v.resize(11);
+ EXPECT_FALSE(allocationIsInternal(v));
+
+ // shrinking back to internal alloc succeeds
+ // note that shrinking further will succeed, but is a waste
+ v.resize(10);
+ v.shrink_to_fit();
+ EXPECT_TRUE(allocationIsInternal(v));
+}
+
+TEST(FatVector, destructorInternal) {
+ int count = 0;
+ {
+ // push 1 into external allocation, verify destruction happens once
+ FatVector<TestUtils::SignalingDtor, 0> v;
+ v.emplace_back(&count);
+ EXPECT_FALSE(allocationIsInternal(v));
+ EXPECT_EQ(0, count);
+ }
+ EXPECT_EQ(1, count);
+}
+
+TEST(FatVector, destructorExternal) {
+ int count = 0;
+ {
+ // push 10 into internal allocation, verify 10 destructors called
+ FatVector<TestUtils::SignalingDtor, 10> v;
+ for (int i = 0; i < 10; i++) {
+ v.emplace_back(&count);
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+ EXPECT_EQ(0, count);
+ }
+ EXPECT_EQ(10, count);
+}
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 02cd77a..0f6b249 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,6 +17,8 @@
#include <gtest/gtest.h>
#include <utils/LinearAllocator.h>
+#include <unit_tests/TestUtils.h>
+
using namespace android;
using namespace android::uirenderer;
@@ -25,27 +27,6 @@
int two = 2;
};
-class SignalingDtor {
-public:
- SignalingDtor() {
- mDestroyed = nullptr;
- }
- SignalingDtor(bool* destroyedSignal) {
- mDestroyed = destroyedSignal;
- *mDestroyed = false;
- }
- virtual ~SignalingDtor() {
- if (mDestroyed) {
- *mDestroyed = true;
- }
- }
- void setSignal(bool* destroyedSignal) {
- mDestroyed = destroyedSignal;
- }
-private:
- bool* mDestroyed;
-};
-
TEST(LinearAllocator, alloc) {
LinearAllocator la;
EXPECT_EQ(0u, la.usedSize());
@@ -62,31 +43,31 @@
}
TEST(LinearAllocator, dtor) {
- bool destroyed[10];
+ int destroyed[10] = { 0 };
{
LinearAllocator la;
for (int i = 0; i < 5; i++) {
- la.alloc<SignalingDtor>()->setSignal(destroyed + i);
+ la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
la.alloc<SimplePair>();
}
la.alloc(100);
for (int i = 0; i < 5; i++) {
- auto sd = new (la) SignalingDtor(destroyed + 5 + i);
+ auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i);
la.autoDestroy(sd);
new (la) SimplePair();
}
la.alloc(100);
for (int i = 0; i < 10; i++) {
- EXPECT_FALSE(destroyed[i]);
+ EXPECT_EQ(0, destroyed[i]);
}
}
for (int i = 0; i < 10; i++) {
- EXPECT_TRUE(destroyed[i]);
+ EXPECT_EQ(1, destroyed[i]);
}
}
TEST(LinearAllocator, rewind) {
- bool destroyed;
+ int destroyed = 0;
{
LinearAllocator la;
auto addr = la.alloc(100);
@@ -94,17 +75,16 @@
la.rewindIfLastAlloc(addr, 100);
EXPECT_GT(16u, la.usedSize());
size_t emptySize = la.usedSize();
- auto sigdtor = la.alloc<SignalingDtor>();
+ auto sigdtor = la.alloc<TestUtils::SignalingDtor>();
sigdtor->setSignal(&destroyed);
- EXPECT_FALSE(destroyed);
+ EXPECT_EQ(0, destroyed);
EXPECT_LE(emptySize, la.usedSize());
la.rewindIfLastAlloc(sigdtor);
- EXPECT_TRUE(destroyed);
+ EXPECT_EQ(1, destroyed);
EXPECT_EQ(emptySize, la.usedSize());
- destroyed = false;
}
// Checking for a double-destroy case
- EXPECT_EQ(destroyed, false);
+ EXPECT_EQ(1, destroyed);
}
TEST(LinearStdAllocator, simpleAllocate) {
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index d02f89d..ffb575f 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -27,68 +27,67 @@
namespace android {
namespace uirenderer {
+
/**
- * Class that redirects static operation dispatch to virtual methods on a Client class.
+ * Virtual class implemented by each test to redirect static operation / state transitions to
+ * virtual methods.
*
- * The client is recreated for every op (so data cannot be persisted between operations), but the
- * virtual dispatch allows for default behaviors to be specified without enumerating each operation
- * for every test.
+ * Virtual dispatch allows for default behaviors to be specified (very common case in below tests),
+ * and allows Renderer vs Dispatching behavior to be merged.
*
* onXXXOp methods fail by default - tests should override ops they expect
+ * startLayer fails by default - tests should override if expected
* startFrame/endFrame do nothing by default - tests should override to intercept
*/
-template<class CustomClient, class Arg>
-class TestReceiver {
+class TestRendererBase {
public:
-#define CLIENT_METHOD(Type) \
- virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
- class Client {
- public:
- virtual ~Client() {};
- MAP_OPS(CLIENT_METHOD)
+ virtual ~TestRendererBase() {}
+ virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
+ virtual void endLayer() { ADD_FAILURE(); }
+ virtual void startFrame(uint32_t width, uint32_t height) {}
+ virtual void endFrame() {}
- virtual void startFrame(Arg& info) {}
- virtual void endFrame(Arg& info) {}
- };
+ // define virtual defaults for direct
+#define BASE_OP_METHOD(Type) \
+ virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); }
+ MAP_OPS(BASE_OP_METHOD)
+ int getIndex() { return mIndex; }
+protected:
+ int mIndex = 0;
+};
+
+/**
+ * Dispatches all static methods to similar formed methods on renderer, which fail by default but
+ * are overriden by subclasses per test.
+ */
+class TestDispatcher {
+public:
#define DISPATCHER_METHOD(Type) \
- static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \
- CustomClient client; client.on##Type(arg, op, state); \
+ static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \
+ renderer.on##Type(op, state); \
}
- MAP_OPS(DISPATCHER_METHOD)
-
- static void startFrame(Arg& info) {
- CustomClient client;
- client.startFrame(info);
- }
-
- static void endFrame(Arg& info) {
- CustomClient client;
- client.endFrame(info);
- }
+ MAP_OPS(DISPATCHER_METHOD);
};
-class Info {
-public:
- int index = 0;
-};
-// Receiver class which will fail if it receives any ops
-class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {};
+class FailRenderer : public TestRendererBase {};
-class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
+class SimpleTestRenderer : public TestRendererBase {
public:
- void startFrame(Info& info) override {
- EXPECT_EQ(0, info.index++);
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, width);
+ EXPECT_EQ(200u, height);
}
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, info.index++);
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
}
- void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, info.index++);
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
}
- void endFrame(Info& info) override {
- EXPECT_EQ(3, info.index++);
+ void endFrame() override {
+ EXPECT_EQ(3, mIndex++);
}
};
TEST(OpReorderer, simple) {
@@ -97,12 +96,11 @@
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(100, 200, *dl);
- Info info;
- reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
- EXPECT_EQ(4, info.index); // 2 ops + start + end
+ SimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
@@ -113,22 +111,21 @@
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
- Info info;
- reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+ FailRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
}
static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
+class SimpleBatchingTestRenderer : public TestRendererBase {
public:
- void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS);
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
}
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS);
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
}
};
TEST(OpReorderer, simpleBatching) {
@@ -146,18 +143,17 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
- Info info;
- reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
- EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
+ SimpleBatchingTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
}
-class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client {
+class RenderNodeTestRenderer : public TestRendererBase {
public:
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
- switch(info.index++) {
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ switch(mIndex++) {
case 0:
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
@@ -167,7 +163,7 @@
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
break;
default:
- FAIL();
+ ADD_FAILURE();
}
}
};
@@ -196,17 +192,16 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
- OpReorderer reorderer;
- reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
- Info info;
- reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
+ RenderNodeTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client {
+class ClippedTestRenderer : public TestRendererBase {
public:
- void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, info.index++);
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
EXPECT_TRUE(state.computedState.transform.isIdentity());
@@ -221,19 +216,27 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
- OpReorderer reorderer;
- reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+ OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
200, 200, nodes);
- Info info;
- reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info);
+ ClippedTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
+class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, info.index++);
+ OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(180u, width);
+ EXPECT_EQ(180u, height);
+ return nullptr;
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
@@ -242,8 +245,8 @@
expectedTransform.loadTranslate(-10, -10, 0);
EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
}
- void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, info.index++);
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
EXPECT_TRUE(state.computedState.transform.isIdentity());
@@ -256,33 +259,61 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
- Info info;
- reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
- EXPECT_EQ(2, info.index);
+ SaveLayerSimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
}
-// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
-class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
+ * - startLayer2, rect2 endLayer2
+ * - startLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
+class SaveLayerNestedTestRenderer : public TestRendererBase {
public:
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
- const int index = info.index++;
+ OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ const int index = mIndex++;
if (index == 0) {
- EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
- } else if (index == 1) {
- EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
- } else { FAIL(); }
- }
- void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
- const int index = info.index++;
- if (index == 2) {
- EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+ EXPECT_EQ(400u, width);
+ EXPECT_EQ(400u, height);
+ return (OffscreenBuffer*) 0x400;
} else if (index == 3) {
+ EXPECT_EQ(800u, width);
+ EXPECT_EQ(800u, height);
+ return (OffscreenBuffer*) 0x800;
+ } else { ADD_FAILURE(); }
+ return (OffscreenBuffer*) nullptr;
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 6);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(7, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(9, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 1) {
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+ } else if (index == 4) {
+ EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+ } else { ADD_FAILURE(); }
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 5) {
+ EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
- } else { FAIL(); }
+ } else { ADD_FAILURE(); }
}
};
TEST(OpReorderer, saveLayerNested) {
@@ -299,12 +330,11 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(800, 800, *dl);
+ OpReorderer reorderer(800, 800, *dl);
- Info info;
- reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
- EXPECT_EQ(4, info.index);
+ SaveLayerNestedTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex());
}
TEST(OpReorderer, saveLayerContentRejection) {
@@ -319,12 +349,11 @@
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
- Info info;
+ OpReorderer reorderer(200, 200, *dl);
+ FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
- reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
}
} // namespace uirenderer
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c023123..e8cdf46 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -38,7 +38,7 @@
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.restore();
});
- playbackOps(*dl, [](const RecordedOp& op) { FAIL(); });
+ playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
TEST(RecordingCanvas, testSimpleRectRecord) {
@@ -135,7 +135,7 @@
// TODO: add asserts
break;
default:
- FAIL();
+ ADD_FAILURE();
}
});
EXPECT_EQ(3, count);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 99ecc9b..5b09fda 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -39,6 +39,24 @@
class TestUtils {
public:
+ class SignalingDtor {
+ public:
+ SignalingDtor()
+ : mSignal(nullptr) {}
+ SignalingDtor(int* signal)
+ : mSignal(signal) {}
+ void setSignal(int* signal) {
+ mSignal = signal;
+ }
+ ~SignalingDtor() {
+ if (mSignal) {
+ (*mSignal)++;
+ }
+ }
+ private:
+ int* mSignal;
+ };
+
static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
for (int i = 0; i < 16; i++) {
if (!MathUtils::areEqual(a[i], b[i])) {
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
new file mode 100644
index 0000000..c3c16c5a
--- /dev/null
+++ b/libs/hwui/utils/FatVector.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_FAT_VECTOR_H
+#define ANDROID_FAT_VECTOR_H
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+#include <type_traits>
+#include <utils/Log.h>
+
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+template <typename T, size_t SIZE>
+class InlineStdAllocator {
+public:
+ struct Allocation {
+ PREVENT_COPY_AND_ASSIGN(Allocation);
+ public:
+ Allocation() {};
+ // char array instead of T array, so memory is uninitialized, with no destructors run
+ char array[sizeof(T) * SIZE];
+ bool inUse = false;
+ };
+
+ typedef T value_type; // needed to implement std::allocator
+ typedef T* pointer; // needed to implement std::allocator
+
+ InlineStdAllocator(Allocation& allocation)
+ : mAllocation(allocation) {}
+ InlineStdAllocator(const InlineStdAllocator& other)
+ : mAllocation(other.mAllocation) {}
+ ~InlineStdAllocator() {}
+
+ T* allocate(size_t num, const void* = 0) {
+ if (!mAllocation.inUse && num <= SIZE) {
+ mAllocation.inUse = true;
+ return (T*) mAllocation.array;
+ } else {
+ return (T*) malloc(num * sizeof(T));
+ }
+ }
+
+ void deallocate(pointer p, size_t num) {
+ if (p == (T*)mAllocation.array) {
+ mAllocation.inUse = false;
+ } else {
+ // 'free' instead of delete here - destruction handled separately
+ free(p);
+ }
+ }
+ Allocation& mAllocation;
+};
+
+/**
+ * std::vector with SIZE elements preallocated into an internal buffer.
+ *
+ * Useful for avoiding the cost of malloc in cases where only SIZE or
+ * fewer elements are needed in the common case.
+ */
+template <typename T, size_t SIZE>
+class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> {
+public:
+ FatVector() : std::vector<T, InlineStdAllocator<T, SIZE>>(
+ InlineStdAllocator<T, SIZE>(mAllocation)) {
+ this->reserve(SIZE);
+ }
+private:
+ typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_FAT_VECTOR_H
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp
index f51f5df..985f3fb 100644
--- a/libs/hwui/utils/NinePatchImpl.cpp
+++ b/libs/hwui/utils/NinePatchImpl.cpp
@@ -82,7 +82,7 @@
}
} else {
SLOW_CASE:
- canvas->drawBitmapRect(bitmap, &src, dst, &paint);
+ canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
}
}
diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h
new file mode 100644
index 0000000..8d42d7e
--- /dev/null
+++ b/libs/hwui/utils/TimeUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef UTILS_TIMEUTILS_H
+#define UTILS_TIMEUTILS_H
+
+#include <utils/Timers.h>
+
+namespace android {
+namespace uirenderer {
+
+constexpr nsecs_t operator"" _ms (unsigned long long ms) {
+ return milliseconds_to_nanoseconds(ms);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* UTILS_TIMEUTILS_H */
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 4a1d7e7..c9586e4 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -42,15 +42,14 @@
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-// Time to wait between animation frames.
-static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
-
// Time to spend fading out the spot completely.
static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
// Time to spend fading out the pointer completely.
static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+// The number of events to be read at once for DisplayEventReceiver.
+static const int EVENT_BUFFER_SIZE = 100;
// --- PointerController ---
@@ -59,6 +58,13 @@
mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
+ if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+ mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+ Looper::EVENT_INPUT, this, nullptr);
+ } else {
+ ALOGE("Failed to initialize DisplayEventReceiver.");
+ }
+
AutoMutex _l(mLock);
mLocked.animationPending = false;
@@ -416,21 +422,49 @@
void PointerController::handleMessage(const Message& message) {
switch (message.what) {
- case MSG_ANIMATE:
- doAnimate();
- break;
case MSG_INACTIVITY_TIMEOUT:
doInactivityTimeout();
break;
}
}
-void PointerController::doAnimate() {
+int PointerController::handleEvent(int /* fd */, int events, void* /* data */) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ bool gotVsync = false;
+ ssize_t n;
+ nsecs_t timestamp;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+ if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+ timestamp = buf[i].header.timestamp;
+ gotVsync = true;
+ }
+ }
+ }
+ if (gotVsync) {
+ doAnimate(timestamp);
+ }
+ return 1; // keep the callback
+}
+
+void PointerController::doAnimate(nsecs_t timestamp) {
AutoMutex _l(mLock);
bool keepAnimating = false;
mLocked.animationPending = false;
- nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+ nsecs_t frameDelay = timestamp - mLocked.animationTime;
// Animate pointer fade.
if (mLocked.pointerFadeDirection < 0) {
@@ -481,7 +515,7 @@
if (!mLocked.animationPending) {
mLocked.animationPending = true;
mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
- mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ mDisplayEventReceiver.requestNextVsync();
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 24a1681..6d840db 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -28,6 +28,7 @@
#include <utils/RefBase.h>
#include <utils/Looper.h>
#include <utils/String8.h>
+#include <gui/DisplayEventReceiver.h>
#include <SkBitmap.h>
@@ -68,7 +69,8 @@
*
* Handles pointer acceleration and animation.
*/
-class PointerController : public PointerControllerInterface, public MessageHandler {
+class PointerController : public PointerControllerInterface, public MessageHandler,
+ public LooperCallback {
protected:
virtual ~PointerController();
@@ -106,7 +108,6 @@
static const size_t MAX_SPOTS = 12;
enum {
- MSG_ANIMATE,
MSG_INACTIVITY_TIMEOUT,
};
@@ -136,6 +137,8 @@
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ DisplayEventReceiver mDisplayEventReceiver;
+
PointerResources mResources;
struct Locked {
@@ -173,7 +176,8 @@
void setPositionLocked(float x, float y);
void handleMessage(const Message& message);
- void doAnimate();
+ int handleEvent(int fd, int events, void* data);
+ void doAnimate(nsecs_t timestamp);
void doInactivityTimeout();
void startAnimationLocked();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index bf3387b..4d0d1bd 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -78,32 +78,51 @@
*/
public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
+ /**
+ * Bit mask for mFieldsMask indicating the presence of mAltitude.
+ */
+ private static final byte HAS_ALTITUDE_MASK = 1;
+ /**
+ * Bit mask for mFieldsMask indicating the presence of mSpeed.
+ */
+ private static final byte HAS_SPEED_MASK = 2;
+ /**
+ * Bit mask for mFieldsMask indicating the presence of mBearing.
+ */
+ private static final byte HAS_BEARING_MASK = 4;
+ /**
+ * Bit mask for mFieldsMask indicating the presence of mAccuracy.
+ */
+ private static final byte HAS_ACCURACY_MASK = 8;
+ /**
+ * Bit mask for mFieldsMask indicating location is from a mock provider.
+ */
+ private static final byte HAS_MOCK_PROVIDER_MASK = 16;
+
+ // Cached data to make bearing/distance computations more efficient for the case
+ // where distanceTo and bearingTo are called in sequence. Assume this typically happens
+ // on the same thread for caching purposes.
+ private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache
+ = new ThreadLocal<BearingDistanceCache>() {
+ @Override
+ protected BearingDistanceCache initialValue() {
+ return new BearingDistanceCache();
+ }
+ };
+
private String mProvider;
private long mTime = 0;
private long mElapsedRealtimeNanos = 0;
private double mLatitude = 0.0;
private double mLongitude = 0.0;
- private boolean mHasAltitude = false;
private double mAltitude = 0.0f;
- private boolean mHasSpeed = false;
private float mSpeed = 0.0f;
- private boolean mHasBearing = false;
private float mBearing = 0.0f;
- private boolean mHasAccuracy = false;
private float mAccuracy = 0.0f;
private Bundle mExtras = null;
- private boolean mIsFromMockProvider = false;
- // Cache the inputs and outputs of computeDistanceAndBearing
- // so calls to distanceTo() and bearingTo() can share work
- private double mLat1 = 0.0;
- private double mLon1 = 0.0;
- private double mLat2 = 0.0;
- private double mLon2 = 0.0;
- private float mDistance = 0.0f;
- private float mInitialBearing = 0.0f;
- // Scratchpad
- private final float[] mResults = new float[2];
+ // A bitmask of fields present in this object (see HAS_* constants defined above).
+ private byte mFieldsMask = 0;
/**
* Construct a new Location with a named provider.
@@ -131,18 +150,14 @@
mProvider = l.mProvider;
mTime = l.mTime;
mElapsedRealtimeNanos = l.mElapsedRealtimeNanos;
+ mFieldsMask = l.mFieldsMask;
mLatitude = l.mLatitude;
mLongitude = l.mLongitude;
- mHasAltitude = l.mHasAltitude;
mAltitude = l.mAltitude;
- mHasSpeed = l.mHasSpeed;
mSpeed = l.mSpeed;
- mHasBearing = l.mHasBearing;
mBearing = l.mBearing;
- mHasAccuracy = l.mHasAccuracy;
mAccuracy = l.mAccuracy;
mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras);
- mIsFromMockProvider = l.mIsFromMockProvider;
}
/**
@@ -152,18 +167,14 @@
mProvider = null;
mTime = 0;
mElapsedRealtimeNanos = 0;
+ mFieldsMask = 0;
mLatitude = 0;
mLongitude = 0;
- mHasAltitude = false;
mAltitude = 0;
- mHasSpeed = false;
mSpeed = 0;
- mHasBearing = false;
mBearing = 0;
- mHasAccuracy = false;
mAccuracy = 0;
mExtras = null;
- mIsFromMockProvider = false;
}
/**
@@ -257,11 +268,13 @@
int deg = Integer.parseInt(degrees);
double min;
double sec = 0.0;
+ boolean secPresent = false;
if (st.hasMoreTokens()) {
min = Integer.parseInt(minutes);
String seconds = st.nextToken();
sec = Double.parseDouble(seconds);
+ secPresent = true;
} else {
min = Double.parseDouble(minutes);
}
@@ -273,11 +286,15 @@
if ((deg < 0.0) || (deg > 179 && !isNegative180)) {
throw new IllegalArgumentException("coordinate=" + coordinate);
}
- if (min < 0 || min > 59) {
+
+ // min must be in [0, 59] if seconds are present, otherwise [0.0, 60.0)
+ if (min < 0 || min >= 60 || (secPresent && (min > 59))) {
throw new IllegalArgumentException("coordinate=" +
coordinate);
}
- if (sec < 0 || sec > 59) {
+
+ // sec must be in [0.0, 60.0)
+ if (sec < 0 || sec >= 60) {
throw new IllegalArgumentException("coordinate=" +
coordinate);
}
@@ -291,7 +308,7 @@
}
private static void computeDistanceAndBearing(double lat1, double lon1,
- double lat2, double lon2, float[] results) {
+ double lat2, double lon2, BearingDistanceCache results) {
// Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
// using the "Inverse Formula" (section 4)
@@ -376,19 +393,19 @@
}
float distance = (float) (b * A * (sigma - deltaSigma));
- results[0] = distance;
- if (results.length > 1) {
- float initialBearing = (float) Math.atan2(cosU2 * sinLambda,
- cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
- initialBearing *= 180.0 / Math.PI;
- results[1] = initialBearing;
- if (results.length > 2) {
- float finalBearing = (float) Math.atan2(cosU1 * sinLambda,
- -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
- finalBearing *= 180.0 / Math.PI;
- results[2] = finalBearing;
- }
- }
+ results.mDistance = distance;
+ float initialBearing = (float) Math.atan2(cosU2 * sinLambda,
+ cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
+ initialBearing *= 180.0 / Math.PI;
+ results.mInitialBearing = initialBearing;
+ float finalBearing = (float) Math.atan2(cosU1 * sinLambda,
+ -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
+ finalBearing *= 180.0 / Math.PI;
+ results.mFinalBearing = finalBearing;
+ results.mLat1 = lat1;
+ results.mLat2 = lat2;
+ results.mLon1 = lon1;
+ results.mLon2 = lon2;
}
/**
@@ -414,8 +431,16 @@
if (results == null || results.length < 1) {
throw new IllegalArgumentException("results is null or has length < 1");
}
+ BearingDistanceCache cache = sBearingDistanceCache.get();
computeDistanceAndBearing(startLatitude, startLongitude,
- endLatitude, endLongitude, results);
+ endLatitude, endLongitude, cache);
+ results[0] = cache.mDistance;
+ if (results.length > 1) {
+ results[1] = cache.mInitialBearing;
+ if (results.length > 2) {
+ results[2] = cache.mFinalBearing;
+ }
+ }
}
/**
@@ -427,21 +452,14 @@
* @return the approximate distance in meters
*/
public float distanceTo(Location dest) {
+ BearingDistanceCache cache = sBearingDistanceCache.get();
// See if we already have the result
- synchronized (mResults) {
- if (mLatitude != mLat1 || mLongitude != mLon1 ||
- dest.mLatitude != mLat2 || dest.mLongitude != mLon2) {
- computeDistanceAndBearing(mLatitude, mLongitude,
- dest.mLatitude, dest.mLongitude, mResults);
- mLat1 = mLatitude;
- mLon1 = mLongitude;
- mLat2 = dest.mLatitude;
- mLon2 = dest.mLongitude;
- mDistance = mResults[0];
- mInitialBearing = mResults[1];
- }
- return mDistance;
+ if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 ||
+ dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) {
+ computeDistanceAndBearing(mLatitude, mLongitude,
+ dest.mLatitude, dest.mLongitude, cache);
}
+ return cache.mDistance;
}
/**
@@ -455,21 +473,14 @@
* @return the initial bearing in degrees
*/
public float bearingTo(Location dest) {
- synchronized (mResults) {
- // See if we already have the result
- if (mLatitude != mLat1 || mLongitude != mLon1 ||
- dest.mLatitude != mLat2 || dest.mLongitude != mLon2) {
- computeDistanceAndBearing(mLatitude, mLongitude,
- dest.mLatitude, dest.mLongitude, mResults);
- mLat1 = mLatitude;
- mLon1 = mLongitude;
- mLat2 = dest.mLatitude;
- mLon2 = dest.mLongitude;
- mDistance = mResults[0];
- mInitialBearing = mResults[1];
- }
- return mInitialBearing;
+ BearingDistanceCache cache = sBearingDistanceCache.get();
+ // See if we already have the result
+ if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 ||
+ dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) {
+ computeDistanceAndBearing(mLatitude, mLongitude,
+ dest.mLatitude, dest.mLongitude, cache);
}
+ return cache.mInitialBearing;
}
/**
@@ -585,7 +596,7 @@
* True if this location has an altitude.
*/
public boolean hasAltitude() {
- return mHasAltitude;
+ return (mFieldsMask & HAS_ALTITUDE_MASK) != 0;
}
/**
@@ -605,7 +616,7 @@
*/
public void setAltitude(double altitude) {
mAltitude = altitude;
- mHasAltitude = true;
+ mFieldsMask |= HAS_ALTITUDE_MASK;
}
/**
@@ -616,14 +627,14 @@
*/
public void removeAltitude() {
mAltitude = 0.0f;
- mHasAltitude = false;
+ mFieldsMask &= ~HAS_ALTITUDE_MASK;
}
/**
* True if this location has a speed.
*/
public boolean hasSpeed() {
- return mHasSpeed;
+ return (mFieldsMask & HAS_SPEED_MASK) != 0;
}
/**
@@ -642,7 +653,7 @@
*/
public void setSpeed(float speed) {
mSpeed = speed;
- mHasSpeed = true;
+ mFieldsMask |= HAS_SPEED_MASK;
}
/**
@@ -653,14 +664,14 @@
*/
public void removeSpeed() {
mSpeed = 0.0f;
- mHasSpeed = false;
+ mFieldsMask &= ~HAS_SPEED_MASK;
}
/**
* True if this location has a bearing.
*/
public boolean hasBearing() {
- return mHasBearing;
+ return (mFieldsMask & HAS_BEARING_MASK) != 0;
}
/**
@@ -692,7 +703,7 @@
bearing -= 360.0f;
}
mBearing = bearing;
- mHasBearing = true;
+ mFieldsMask |= HAS_BEARING_MASK;
}
/**
@@ -703,7 +714,7 @@
*/
public void removeBearing() {
mBearing = 0.0f;
- mHasBearing = false;
+ mFieldsMask &= ~HAS_BEARING_MASK;
}
/**
@@ -713,7 +724,7 @@
* accuracy.
*/
public boolean hasAccuracy() {
- return mHasAccuracy;
+ return (mFieldsMask & HAS_ACCURACY_MASK) != 0;
}
/**
@@ -751,7 +762,7 @@
*/
public void setAccuracy(float accuracy) {
mAccuracy = accuracy;
- mHasAccuracy = true;
+ mFieldsMask |= HAS_ACCURACY_MASK;
}
/**
@@ -762,7 +773,7 @@
*/
public void removeAccuracy() {
mAccuracy = 0.0f;
- mHasAccuracy = false;
+ mFieldsMask &= ~HAS_ACCURACY_MASK;
}
/**
@@ -780,7 +791,7 @@
@SystemApi
public boolean isComplete() {
if (mProvider == null) return false;
- if (!mHasAccuracy) return false;
+ if (!hasAccuracy()) return false;
if (mTime == 0) return false;
if (mElapsedRealtimeNanos == 0) return false;
return true;
@@ -798,8 +809,8 @@
@SystemApi
public void makeComplete() {
if (mProvider == null) mProvider = "?";
- if (!mHasAccuracy) {
- mHasAccuracy = true;
+ if (!hasAccuracy()) {
+ mFieldsMask |= HAS_ACCURACY_MASK;
mAccuracy = 100.0f;
}
if (mTime == 0) mTime = System.currentTimeMillis();
@@ -838,7 +849,7 @@
s.append("Location[");
s.append(mProvider);
s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude));
- if (mHasAccuracy) s.append(String.format(" acc=%.0f", mAccuracy));
+ if (hasAccuracy()) s.append(String.format(" acc=%.0f", mAccuracy));
else s.append(" acc=???");
if (mTime == 0) {
s.append(" t=?!?");
@@ -849,10 +860,10 @@
s.append(" et=");
TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s);
}
- if (mHasAltitude) s.append(" alt=").append(mAltitude);
- if (mHasSpeed) s.append(" vel=").append(mSpeed);
- if (mHasBearing) s.append(" bear=").append(mBearing);
- if (mIsFromMockProvider) s.append(" mock");
+ if (hasAltitude()) s.append(" alt=").append(mAltitude);
+ if (hasSpeed()) s.append(" vel=").append(mSpeed);
+ if (hasBearing()) s.append(" bear=").append(mBearing);
+ if (isFromMockProvider()) s.append(" mock");
if (mExtras != null) {
s.append(" {").append(mExtras).append('}');
@@ -873,18 +884,14 @@
Location l = new Location(provider);
l.mTime = in.readLong();
l.mElapsedRealtimeNanos = in.readLong();
+ l.mFieldsMask = in.readByte();
l.mLatitude = in.readDouble();
l.mLongitude = in.readDouble();
- l.mHasAltitude = in.readInt() != 0;
l.mAltitude = in.readDouble();
- l.mHasSpeed = in.readInt() != 0;
l.mSpeed = in.readFloat();
- l.mHasBearing = in.readInt() != 0;
l.mBearing = in.readFloat();
- l.mHasAccuracy = in.readInt() != 0;
l.mAccuracy = in.readFloat();
l.mExtras = in.readBundle();
- l.mIsFromMockProvider = in.readInt() != 0;
return l;
}
@@ -904,18 +911,14 @@
parcel.writeString(mProvider);
parcel.writeLong(mTime);
parcel.writeLong(mElapsedRealtimeNanos);
+ parcel.writeByte(mFieldsMask);
parcel.writeDouble(mLatitude);
parcel.writeDouble(mLongitude);
- parcel.writeInt(mHasAltitude ? 1 : 0);
parcel.writeDouble(mAltitude);
- parcel.writeInt(mHasSpeed ? 1 : 0);
parcel.writeFloat(mSpeed);
- parcel.writeInt(mHasBearing ? 1 : 0);
parcel.writeFloat(mBearing);
- parcel.writeInt(mHasAccuracy ? 1 : 0);
parcel.writeFloat(mAccuracy);
parcel.writeBundle(mExtras);
- parcel.writeInt(mIsFromMockProvider? 1 : 0);
}
/**
@@ -940,7 +943,7 @@
* Attaches an extra {@link Location} to this Location.
*
* @param key the key associated with the Location extra
- * @param location the Location to attach
+ * @param value the Location to attach
* @hide
*/
public void setExtraLocation(String key, Location value) {
@@ -956,7 +959,7 @@
* @return true if this Location came from a mock provider, false otherwise
*/
public boolean isFromMockProvider() {
- return mIsFromMockProvider;
+ return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0;
}
/**
@@ -967,6 +970,24 @@
*/
@SystemApi
public void setIsFromMockProvider(boolean isFromMockProvider) {
- mIsFromMockProvider = isFromMockProvider;
+ if (isFromMockProvider) {
+ mFieldsMask |= HAS_MOCK_PROVIDER_MASK;
+ } else {
+ mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK;
+ }
+ }
+
+ /**
+ * Caches data used to compute distance and bearing (so successive calls to {@link #distanceTo}
+ * and {@link #bearingTo} don't duplicate work.
+ */
+ private static class BearingDistanceCache {
+ private double mLat1 = 0.0;
+ private double mLon1 = 0.0;
+ private double mLat2 = 0.0;
+ private double mLon2 = 0.0;
+ private float mDistance = 0.0f;
+ private float mInitialBearing = 0.0f;
+ private float mFinalBearing = 0.0f;
}
}
diff --git a/location/tests/locationtests/src/android/location/LocationTest.java b/location/tests/locationtests/src/android/location/LocationTest.java
index 847ac7a..dc8d0f7 100644
--- a/location/tests/locationtests/src/android/location/LocationTest.java
+++ b/location/tests/locationtests/src/android/location/LocationTest.java
@@ -16,6 +16,7 @@
package android.location;
+import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
@@ -225,6 +226,40 @@
assertEquals(message, loc.getBearing(), 0, 0);
}
+ public void testParcel() {
+ final double expectedLat = 33;
+ final double expectedLon = -122;
+ final float expectedAccuracy = 15;
+ final float expectedSpeed = 5;
+ Location loc = new Location("test");
+ loc.setLatitude(expectedLat);
+ loc.setLongitude(expectedLon);
+ loc.setAccuracy(expectedAccuracy);
+ loc.setSpeed(expectedSpeed);
+
+ // Serialize location object into bytes via parcelable capability
+ Parcel parcel = Parcel.obtain();
+ loc.writeToParcel(parcel, 0);
+ byte[] rawBytes = parcel.marshall();
+ parcel.recycle();
+
+ // Turn the bytes back into a location object
+ parcel = Parcel.obtain();
+ parcel.unmarshall(rawBytes, 0, rawBytes.length);
+ parcel.setDataPosition(0);
+ Location deserialized = Location.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertEquals(expectedLat, deserialized.getLatitude());
+ assertEquals(expectedLon, deserialized.getLongitude());
+ assertEquals(expectedAccuracy, deserialized.getAccuracy());
+ assertTrue(deserialized.hasAccuracy());
+ assertEquals(expectedSpeed, deserialized.getSpeed());
+ assertTrue(deserialized.hasSpeed());
+ assertFalse(deserialized.hasBearing());
+ assertFalse(deserialized.hasAltitude());
+ assertFalse(deserialized.isFromMockProvider());
+ }
}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index ba867e1..e0a8026 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -249,7 +249,7 @@
*/
public @NonNull String getRoot() {
if (!isConnected()) {
- throw new IllegalStateException("getSessionToken() called while not connected (state="
+ throw new IllegalStateException("getRoot() called while not connected (state="
+ getStateLabel(mState) + ")");
}
return mRootId;
@@ -776,68 +776,88 @@
*/
private class MediaServiceConnection implements ServiceConnection {
@Override
- public void onServiceConnected(ComponentName name, IBinder binder) {
- if (DBG) {
- Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
- + " binder=" + binder);
- dump();
- }
+ public void onServiceConnected(final ComponentName name, final IBinder binder) {
+ postOrRun(new Runnable() {
+ @Override
+ public void run() {
+ if (DBG) {
+ Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
+ + " binder=" + binder);
+ dump();
+ }
- // Make sure we are still the current connection, and that they haven't called
- // disconnect().
- if (!isCurrent("onServiceConnected")) {
- return;
- }
+ // Make sure we are still the current connection, and that they haven't called
+ // disconnect().
+ if (!isCurrent("onServiceConnected")) {
+ return;
+ }
- // Save their binder
- mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
+ // Save their binder
+ mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
- // We make a new mServiceCallbacks each time we connect so that we can drop
- // responses from previous connections.
- mServiceCallbacks = getNewServiceCallbacks();
- mState = CONNECT_STATE_CONNECTING;
+ // We make a new mServiceCallbacks each time we connect so that we can drop
+ // responses from previous connections.
+ mServiceCallbacks = getNewServiceCallbacks();
+ mState = CONNECT_STATE_CONNECTING;
- // Call connect, which is async. When we get a response from that we will
- // say that we're connected.
- try {
- if (DBG) {
- Log.d(TAG, "ServiceCallbacks.onConnect...");
- dump();
+ // Call connect, which is async. When we get a response from that we will
+ // say that we're connected.
+ try {
+ if (DBG) {
+ Log.d(TAG, "ServiceCallbacks.onConnect...");
+ dump();
+ }
+ mServiceBinder.connect(mContext.getPackageName(), mRootHints,
+ mServiceCallbacks);
+ } catch (RemoteException ex) {
+ // Connect failed, which isn't good. But the auto-reconnect on the service
+ // will take over and we will come back. We will also get the
+ // onServiceDisconnected, which has all the cleanup code. So let that do
+ // it.
+ Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+ if (DBG) {
+ Log.d(TAG, "ServiceCallbacks.onConnect...");
+ dump();
+ }
+ }
}
- mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks);
- } catch (RemoteException ex) {
- // Connect failed, which isn't good. But the auto-reconnect on the service
- // will take over and we will come back. We will also get the
- // onServiceDisconnected, which has all the cleanup code. So let that do it.
- Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
- if (DBG) {
- Log.d(TAG, "ServiceCallbacks.onConnect...");
- dump();
- }
- }
+ });
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- if (DBG) {
- Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
- + " this=" + this + " mServiceConnection=" + mServiceConnection);
- dump();
+ public void onServiceDisconnected(final ComponentName name) {
+ postOrRun(new Runnable() {
+ @Override
+ public void run() {
+ if (DBG) {
+ Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
+ + " this=" + this + " mServiceConnection=" + mServiceConnection);
+ dump();
+ }
+
+ // Make sure we are still the current connection, and that they haven't called
+ // disconnect().
+ if (!isCurrent("onServiceDisconnected")) {
+ return;
+ }
+
+ // Clear out what we set in onServiceConnected
+ mServiceBinder = null;
+ mServiceCallbacks = null;
+
+ // And tell the app that it's suspended.
+ mState = CONNECT_STATE_SUSPENDED;
+ mCallback.onConnectionSuspended();
+ }
+ });
+ }
+
+ private void postOrRun(Runnable r) {
+ if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+ r.run();
+ } else {
+ mHandler.post(r);
}
-
- // Make sure we are still the current connection, and that they haven't called
- // disconnect().
- if (!isCurrent("onServiceDisconnected")) {
- return;
- }
-
- // Clear out what we set in onServiceConnected
- mServiceBinder = null;
- mServiceCallbacks = null;
-
- // And tell the app that it's suspended.
- mState = CONNECT_STATE_SUSPENDED;
- mCallback.onConnectionSuspended();
}
/**
diff --git a/opengl/java/android/opengl/GLES31.java b/opengl/java/android/opengl/GLES31.java
index 3cbaa60..805930e 100644
--- a/opengl/java/android/opengl/GLES31.java
+++ b/opengl/java/android/opengl/GLES31.java
@@ -24,9 +24,14 @@
public static final int GL_VERTEX_SHADER_BIT = 0x00000001;
public static final int GL_FRAGMENT_SHADER_BIT = 0x00000002;
+ public static final int GL_COMPUTE_SHADER_BIT = 0x00000020;
+ public static final int GL_ALL_SHADER_BITS = -1; // 0xFFFFFFFF
+
+ public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 0x00000001;
+ public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 0x00000002;
public static final int GL_UNIFORM_BARRIER_BIT = 0x00000004;
public static final int GL_TEXTURE_FETCH_BARRIER_BIT = 0x00000008;
- public static final int GL_COMPUTE_SHADER_BIT = 0x00000020;
+ public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 0x00000020;
public static final int GL_COMMAND_BARRIER_BIT = 0x00000040;
public static final int GL_PIXEL_BUFFER_BARRIER_BIT = 0x00000080;
public static final int GL_TEXTURE_UPDATE_BARRIER_BIT = 0x00000100;
@@ -35,7 +40,8 @@
public static final int GL_TRANSFORM_FEEDBACK_BARRIER_BIT = 0x00000800;
public static final int GL_ATOMIC_COUNTER_BARRIER_BIT = 0x00001000;
public static final int GL_SHADER_STORAGE_BARRIER_BIT = 0x00002000;
- public static final int GL_ALL_SHADER_BITS = -1; // 0xFFFFFFFF
+ public static final int GL_ALL_BARRIER_BITS = -1; // 0xFFFFFFFF
+
public static final int GL_TEXTURE_WIDTH = 0x1000;
public static final int GL_TEXTURE_HEIGHT = 0x1001;
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index f9e8027..d45345e 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -40,7 +40,7 @@
<activity
android:name=".ManageRootActivity"
- android:theme="@style/DocumentsNonDialogTheme"
+ android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_doc_text">
<intent-filter>
<action android:name="android.provider.action.MANAGE_ROOT" />
@@ -63,7 +63,7 @@
<activity
android:name=".FilesActivity"
- android:theme="@style/FilesTheme"
+ android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_files_app"
android:label="@string/files_label"
android:documentLaunchMode="intoExisting">
diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
index ab414a9..bf19d4e 100644
--- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
@@ -16,10 +16,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
- android:state_focused="true"
- android:color="@color/platform_blue_a200"
- android:alpha="0.1" />
- <item
android:state_activated="true"
android:color="?android:attr/colorAccent"
android:alpha="0.1" />
diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml
index d415a84..a8dcbb0 100644
--- a/packages/DocumentsUI/res/values-sw720dp/styles.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/styles.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Dialog">
+ <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Light.Dialog">
<!-- We do not specify width of window here because the max size of
floating window specified by windowFixedWidthis is limited. -->
<item name="*android:windowFixedHeightMajor">80%</item>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index cb6957d..a376418 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -15,23 +15,15 @@
-->
<resources>
- <color name="material_grey_50">#fffafafa</color>
- <color name="material_grey_300">#ffeeeeee</color>
- <color name="material_grey_600">#ff757575</color>
- <color name="material_grey_800">#ff424242</color>
+ <color name="material_grey_400">#ffbdbdbd</color>
- <color name="primary_dark">@*android:color/material_blue_grey_900</color>
- <color name="primary">@*android:color/material_blue_grey_800</color>
- <color name="accent">@*android:color/material_deep_teal_500</color>
-
- <color name="platform_blue_100">#ffd0d9ff</color>
- <color name="platform_blue_500">#ff5677fc</color>
- <color name="platform_blue_700">#ff455ede</color>
- <color name="platform_blue_a100">#ffa6baff</color>
- <color name="platform_blue_a200">#ffff5177</color>
+ <color name="primary_dark">@*android:color/primary_dark_material_dark</color>
+ <color name="primary">@*android:color/material_blue_grey_900</color>
+ <color name="accent">@*android:color/accent_material_light</color>
+ <color name="action_mode">@color/material_grey_400</color>
- <color name="directory_background">@color/material_grey_300</color>
- <color name="item_doc_grid_background">#FFFFFFFF</color>
+ <color name="directory_background">@*android:color/material_grey_300</color>
+ <color name="item_doc_grid_background">@android:color/white</color>
<color name="item_doc_grid_protect_background">#88000000</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 c13f144..15d17cc 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -28,7 +28,7 @@
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorAccent">@color/accent</item>
- <item name="colorActionMode">@color/material_grey_800</item>
+ <item name="colorActionMode">@color/action_mode</item>
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
@@ -37,14 +37,17 @@
<item name="android:windowNoTitle">true</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
- <item name="android:alertDialogTheme">@android:style/Theme.Material.Light.Dialog.Alert</item>
</style>
- <style name="DocumentsBaseTheme.FullScreen" parent="@style/Theme.AppCompat.Light.DarkActionBar">
+ <style name="DocumentsFullScreenTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
<item name="actionBarWidgetTheme">@null</item>
<item name="actionBarTheme">@style/ActionBarTheme</item>
<item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
- <item name="colorActionMode">@color/material_grey_800</item>
+
+ <item name="android:colorPrimaryDark">@color/primary_dark</item>
+ <item name="android:colorPrimary">@color/primary</item>
+ <item name="android:colorAccent">@color/accent</item>
+ <item name="colorActionMode">@color/action_mode</item>
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
@@ -55,38 +58,6 @@
<item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
</style>
- <style name="DocumentsNonDialogTheme" parent="@style/DocumentsBaseTheme.FullScreen">
- <item name="android:colorPrimaryDark">@color/primary_dark</item>
- <item name="android:colorPrimary">@color/primary</item>
- <item name="android:colorAccent">@color/accent</item>
-
- <item name="android:actionModeStyle">@style/ActionModeStyle</item>
-
- <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
- </style>
-
- <style name="ActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
- <item name="android:background">@color/material_grey_600</item>
- </style>
-
- <style name="AlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
- <item name="android:colorAccent">@color/platform_blue_700</item>
- </style>
-
- <style name="FilesTheme" parent="@style/DocumentsBaseTheme.FullScreen">
- <item name="android:colorPrimaryDark">@color/platform_blue_700</item>
- <item name="android:colorPrimary">@color/platform_blue_500</item>
- <item name="android:colorAccent">@color/platform_blue_700</item>
- <item name="colorControlActivated">@color/platform_blue_a100</item>
- <item name="android:actionModeStyle">@style/FilesActionModeStyle</item>
- <item name="colorActionMode">@color/platform_blue_700</item>
- <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
- </style>
-
- <style name="FilesActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
- <item name="android:background">@color/platform_blue_100</item>
- </style>
-
<style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
<item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
<item name="android:minHeight">3dp</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 45a8907..a09a22a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -17,7 +17,6 @@
package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.State.ACTION_BROWSE;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_MANAGE;
import static com.android.documentsui.State.MODE_GRID;
@@ -55,7 +54,6 @@
import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.Parcelable;
-import android.os.SystemProperties;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
@@ -95,6 +93,7 @@
import com.android.documentsui.MultiSelectManager.Selection;
import com.android.documentsui.ProviderExecutor.Preemptable;
import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.dirlist.FragmentTuner;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
@@ -341,7 +340,7 @@
mType = getArguments().getInt(EXTRA_TYPE);
mStateKey = buildStateKey(root, doc);
- mFragmentTuner = pickFragmentTuner(state);
+ mFragmentTuner = FragmentTuner.pick(state);
mClipper = new DocumentClipper(context);
if (mType == TYPE_RECENT_OPEN) {
@@ -429,7 +428,6 @@
// Kick off loader at least once
getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
- mFragmentTuner.afterActivityCreated(this);
updateDisplayState();
}
@@ -1638,21 +1636,6 @@
}
}
- private FragmentTuner pickFragmentTuner(final State state) {
- return state.action == ACTION_BROWSE
- ? new FilesTuner()
- : new DefaultTuner(state.action);
- }
-
- /**
- * Interface for specializing the Fragment for the "host" Activity.
- * Feel free to expand the role of this class to handle other specializations.
- */
- private interface FragmentTuner {
- void updateActionMenu(Menu menu, int dirType, boolean canDelete);
- void afterActivityCreated(DirectoryFragment fragment);
- }
-
/**
* Abstract task providing support for loading documents *off*
* the main thread. And if it isn't obvious, creating a list
@@ -1673,65 +1656,6 @@
abstract void onDocumentsReady(List<DocumentInfo> docs);
}
- /**
- * Provides support for Platform specific specializations of DirectoryFragment.
- */
- private static final class DefaultTuner implements FragmentTuner {
-
- private final boolean mManaging;
-
- public DefaultTuner(int action) {
- mManaging = (action == ACTION_MANAGE);
- }
-
- @Override
- public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
- boolean copyEnabled = mManaging && dirType != TYPE_RECENT_OPEN;
- // TODO: The selection needs to be deletable.
- boolean moveEnabled =
- SystemProperties.getBoolean("debug.documentsui.enable_move", false);
- menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
-
- final MenuItem open = menu.findItem(R.id.menu_open);
- final MenuItem share = menu.findItem(R.id.menu_share);
- final MenuItem delete = menu.findItem(R.id.menu_delete);
- final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
- final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
-
- open.setVisible(!mManaging);
- share.setVisible(mManaging);
- delete.setVisible(mManaging && canDelete);
- copyTo.setVisible(copyEnabled);
- copyTo.setEnabled(copyEnabled);
- moveTo.setVisible(moveEnabled);
- moveTo.setEnabled(moveEnabled);
- }
-
- @Override
- public void afterActivityCreated(DirectoryFragment fragment) {}
- }
-
- /**
- * Provides support for Files activity specific specializations of DirectoryFragment.
- */
- private static final class FilesTuner implements FragmentTuner {
- @Override
- public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
-
- menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(dirType != TYPE_RECENT_OPEN);
-
- menu.findItem(R.id.menu_share).setVisible(true);
- menu.findItem(R.id.menu_delete).setVisible(canDelete);
-
- menu.findItem(R.id.menu_open).setVisible(false);
- menu.findItem(R.id.menu_copy_to).setVisible(true);
- menu.findItem(R.id.menu_move_to).setVisible(true);
- }
-
- @Override
- public void afterActivityCreated(DirectoryFragment fragment) {}
- }
-
boolean isSelected(int position) {
return mSelectionManager.getSelection().contains(position);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index aae5269..18957ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -86,7 +86,7 @@
mShowAsDialog = res.getBoolean(R.bool.show_as_dialog);
if (!mShowAsDialog) {
- setTheme(R.style.DocumentsNonDialogTheme);
+ setTheme(R.style.DocumentsFullScreenTheme);
}
if (mShowAsDialog) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Menus.java b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
index 3f43a3d..5277d2b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Menus.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Menus.java
@@ -19,14 +19,14 @@
import android.view.Menu;
import android.view.MenuItem;
-final class Menus {
+public final class Menus {
private Menus() {}
/**
* Disables hidden menu items so that they are not invokable via command shortcuts
*/
- static void disableHiddenItems(Menu menu, MenuItem... exclusions) {
+ public static void disableHiddenItems(Menu menu, MenuItem... exclusions) {
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.isVisible()) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 0dcd02d..d75b6fd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -287,33 +287,24 @@
super(context, 0);
final List<RootItem> libraries = new ArrayList<>();
- final List<RootItem> clouds = new ArrayList<>();
- final List<RootItem> locals = new ArrayList<>();
+ final List<RootItem> others = new ArrayList<>();
- for (RootInfo root : roots) {
- RootItem item = new RootItem(root);
- switch (root.derivedType) {
- case RootInfo.TYPE_LOCAL:
- locals.add(item);
- break;
- case RootInfo.TYPE_CLOUD:
- clouds.add(item);
- break;
- default:
- libraries.add(item);
- break;
+ for (final RootInfo root : roots) {
+ final RootItem item = new RootItem(root);
+ if (root.isLibrary()) {
+ libraries.add(item);
+ } else {
+ others.add(item);
}
}
final RootComparator comp = new RootComparator();
Collections.sort(libraries, comp);
- Collections.sort(locals, comp);
- Collections.sort(clouds, comp);
+ Collections.sort(others, comp);
addAll(libraries);
add(new SpacerItem());
- addAll(locals);
- addAll(clouds);
+ addAll(others);
if (includeApps != null) {
final PackageManager pm = context.getPackageManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
new file mode 100644
index 0000000..ca85cff
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.State.ACTION_BROWSE;
+import static com.android.documentsui.State.ACTION_MANAGE;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.os.SystemProperties;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.documentsui.DirectoryFragment;
+import com.android.documentsui.Menus;
+import com.android.documentsui.R;
+import com.android.documentsui.State;
+
+/**
+ * Providers support for specializing the DirectoryFragment to the "host" Activity.
+ * Feel free to expand the role of this class to handle other specializations.
+ */
+public abstract class FragmentTuner {
+ public static FragmentTuner pick(State state) {
+ switch (state.action) {
+ case ACTION_BROWSE:
+ return new FilesTuner();
+ case ACTION_MANAGE:
+ return new ManageTuner();
+ default:
+ return new DocumentsTuner();
+ }
+ }
+
+ public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete);
+
+ /**
+ * Provides support for Platform specific specializations of DirectoryFragment.
+ */
+ private static final class DocumentsTuner extends FragmentTuner {
+ @Override
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+
+ boolean copyEnabled = dirType != DirectoryFragment.TYPE_RECENT_OPEN;
+ boolean moveEnabled =
+ SystemProperties.getBoolean("debug.documentsui.enable_move", false);
+ menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled);
+
+ final MenuItem open = menu.findItem(R.id.menu_open);
+ final MenuItem share = menu.findItem(R.id.menu_share);
+ final MenuItem delete = menu.findItem(R.id.menu_delete);
+ final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+ final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+
+ open.setVisible(true);
+ share.setVisible(false);
+ delete.setVisible(false);
+ copyTo.setVisible(copyEnabled);
+ copyTo.setEnabled(copyEnabled);
+ moveTo.setVisible(moveEnabled);
+ moveTo.setEnabled(moveEnabled);
+ }
+ }
+
+ /**
+ * Provides support for Platform specific specializations of DirectoryFragment.
+ */
+ private static final class ManageTuner extends FragmentTuner {
+
+ @Override
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ checkArgument(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+
+ boolean moveEnabled =
+ SystemProperties.getBoolean("debug.documentsui.enable_move", false);
+ menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(true);
+
+ final MenuItem open = menu.findItem(R.id.menu_open);
+ final MenuItem share = menu.findItem(R.id.menu_share);
+ final MenuItem delete = menu.findItem(R.id.menu_delete);
+ final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+ final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+
+ open.setVisible(false);
+ share.setVisible(false);
+ delete.setVisible(canDelete);
+ copyTo.setVisible(true);
+ copyTo.setEnabled(true);
+ moveTo.setVisible(moveEnabled);
+ moveTo.setEnabled(moveEnabled);
+ }
+ }
+
+ /**
+ * Provides support for Files activity specific specializations of DirectoryFragment.
+ */
+ private static final class FilesTuner extends FragmentTuner {
+ @Override
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+
+ MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
+ MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
+ copy.setEnabled(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+
+ menu.findItem(R.id.menu_share).setVisible(true);
+ menu.findItem(R.id.menu_delete).setVisible(canDelete);
+
+ menu.findItem(R.id.menu_open).setVisible(false);
+ menu.findItem(R.id.menu_copy_to).setVisible(true);
+ menu.findItem(R.id.menu_move_to).setVisible(true);
+
+ Menus.disableHiddenItems(menu, copy, paste);
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 17e6a80..501392c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -51,7 +51,8 @@
public static final int TYPE_RECENTS = 4;
public static final int TYPE_DOWNLOADS = 5;
public static final int TYPE_LOCAL = 6;
- public static final int TYPE_CLOUD = 7;
+ public static final int TYPE_MTP = 7;
+ public static final int TYPE_CLOUD = 8;
public String authority;
public String rootId;
@@ -184,6 +185,8 @@
derivedType = TYPE_AUDIO;
} else if (isRecents()) {
derivedType = TYPE_RECENTS;
+ } else if (isMtp()) {
+ derivedType = TYPE_MTP;
} else {
derivedType = TYPE_CLOUD;
}
@@ -216,6 +219,15 @@
&& "audio_root".equals(rootId);
}
+ public boolean isMtp() {
+ return "com.android.mtp.documents".equals(authority);
+ }
+
+ public boolean isLibrary() {
+ return derivedType == TYPE_IMAGES || derivedType == TYPE_VIDEO || derivedType == TYPE_AUDIO
+ || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
+ }
+
@Override
public String toString() {
return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
deleted file mode 100644
index 8baadba..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := TestDocumentsProvider
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-#LOCAL_SDK_VERSION := current
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
deleted file mode 100644
index 66988a1..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.documentsui.testing">
- <application>
- <provider android:name="TestDocumentsProvider"
- android:authorities="com.android.documentsui.testing"
- android:exported="true"
- android:grantUriPermissions="true"
- android:permission="android.permission.MANAGE_DOCUMENTS">
- <intent-filter>
- <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
- </intent-filter>
- </provider>
- </application>
-</manifest>
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
deleted file mode 100644
index 63ff0de5..0000000
--- a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.testing;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.os.AsyncTask;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsProvider;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TestDocumentsProvider extends DocumentsProvider {
- private static final String TAG = "TestDocumentsProvider";
-
- private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
- Root.COLUMN_ROOT_ID,
- Root.COLUMN_FLAGS,
- Root.COLUMN_ICON,
- Root.COLUMN_TITLE,
- Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
- };
-
- private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
- Document.COLUMN_DOCUMENT_ID,
- Document.COLUMN_MIME_TYPE,
- Document.COLUMN_DISPLAY_NAME,
- Document.COLUMN_LAST_MODIFIED,
- Document.COLUMN_FLAGS,
- Document.COLUMN_SIZE,
- };
-
- private static String[] resolveRootProjection(String[] projection) {
- return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
- }
-
- private static String[] resolveDocumentProjection(String[] projection) {
- return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
- }
-
- @Override
- public boolean onCreate() {
- resetRoots();
- return true;
- }
-
- @Override
- public Cursor queryRoots(String[] projection) throws FileNotFoundException {
- final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
-
- RowBuilder row = result.newRow();
- row.add(Root.COLUMN_ROOT_ID, "local");
- row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
- row.add(Root.COLUMN_TITLE, "TEST-Local");
- row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary");
- row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
-
- row = result.newRow();
- row.add(Root.COLUMN_ROOT_ID, "create");
- row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
- row.add(Root.COLUMN_TITLE, "TEST-Create");
- row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
-
- return result;
- }
-
- private Map<String, Doc> mDocs = new HashMap<>();
-
- private Doc mLocalRoot;
- private Doc mCreateRoot;
-
- private Doc buildDoc(String docId, String displayName, String mimeType) {
- final Doc doc = new Doc();
- doc.docId = docId;
- doc.displayName = displayName;
- doc.mimeType = mimeType;
- mDocs.put(doc.docId, doc);
- return doc;
- }
-
- public void resetRoots() {
- Log.d(TAG, "resetRoots()");
-
- mDocs.clear();
-
- mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR);
-
- mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR);
- mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
-
- {
- Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1");
- file1.contents = "fileone".getBytes();
- file1.flags = Document.FLAG_SUPPORTS_WRITE;
- mLocalRoot.children.add(file1);
- mCreateRoot.children.add(file1);
- }
-
- {
- Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2");
- file2.contents = "filetwo".getBytes();
- file2.flags = Document.FLAG_SUPPORTS_WRITE;
- mLocalRoot.children.add(file2);
- mCreateRoot.children.add(file2);
- }
-
- Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR);
- mLocalRoot.children.add(dir1);
-
- {
- Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3");
- file3.contents = "filethree".getBytes();
- file3.flags = Document.FLAG_SUPPORTS_WRITE;
- dir1.children.add(file3);
- }
-
- Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR);
- mCreateRoot.children.add(dir2);
-
- {
- Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4");
- file4.contents = "filefour".getBytes();
- file4.flags = Document.FLAG_SUPPORTS_WRITE;
- dir2.children.add(file4);
- }
- }
-
- private static class Doc {
- public String docId;
- public int flags;
- public String displayName;
- public long size;
- public String mimeType;
- public long lastModified;
- public byte[] contents;
- public List<Doc> children = new ArrayList<>();
-
- public void include(MatrixCursor result) {
- final RowBuilder row = result.newRow();
- row.add(Document.COLUMN_DOCUMENT_ID, docId);
- row.add(Document.COLUMN_DISPLAY_NAME, displayName);
- row.add(Document.COLUMN_SIZE, size);
- row.add(Document.COLUMN_MIME_TYPE, mimeType);
- row.add(Document.COLUMN_FLAGS, flags);
- row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
- }
- }
-
- @Override
- public boolean isChildDocument(String parentDocumentId, String documentId) {
- for (Doc doc : mDocs.get(parentDocumentId).children) {
- if (doc.docId.equals(documentId)) {
- return true;
- }
- if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
- return isChildDocument(doc.docId, documentId);
- }
- }
- return false;
- }
-
- @Override
- public String createDocument(String parentDocumentId, String mimeType, String displayName)
- throws FileNotFoundException {
- final String docId = "doc:" + System.currentTimeMillis();
- final Doc doc = buildDoc(docId, displayName, mimeType);
- doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
- mDocs.get(parentDocumentId).children.add(doc);
- return docId;
- }
-
- @Override
- public String renameDocument(String documentId, String displayName)
- throws FileNotFoundException {
- mDocs.get(documentId).displayName = displayName;
- return null;
- }
-
- @Override
- public void deleteDocument(String documentId) throws FileNotFoundException {
- mDocs.remove(documentId);
- for (Doc doc : mDocs.values()) {
- doc.children.remove(documentId);
- }
- }
-
- @Override
- public Cursor queryDocument(String documentId, String[] projection)
- throws FileNotFoundException {
- final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
- mDocs.get(documentId).include(result);
- return result;
- }
-
- @Override
- public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
- String sortOrder) throws FileNotFoundException {
- final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
- for (Doc doc : mDocs.get(parentDocumentId).children) {
- doc.include(result);
- }
- return result;
- }
-
- @Override
- public ParcelFileDescriptor openDocument(String documentId, String mode,
- CancellationSignal signal) throws FileNotFoundException {
- final Doc doc = mDocs.get(documentId);
- if (doc == null) {
- throw new FileNotFoundException();
- }
- final ParcelFileDescriptor[] pipe;
- try {
- pipe = ParcelFileDescriptor.createPipe();
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- if (mode.contains("w")) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (doc) {
- try {
- final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
- pipe[0]);
- doc.contents = readFullyNoClose(is);
- is.close();
- doc.notifyAll();
- } catch (IOException e) {
- Log.w(TAG, "Failed to stream", e);
- }
- }
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
- return pipe[1];
- } else {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (doc) {
- try {
- final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
- pipe[1]);
- while (doc.contents == null) {
- doc.wait();
- }
- os.write(doc.contents);
- os.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to stream", e);
- } catch (InterruptedException e) {
- Log.w(TAG, "Interuppted", e);
- }
- }
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
- return pipe[0];
- }
- }
-
- private static byte[] readFullyNoClose(InputStream in) throws IOException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int count;
- while ((count = in.read(buffer)) != -1) {
- bytes.write(buffer, 0, count);
- }
- return bytes.toByteArray();
- }
-}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index cf486b1..2a540d4 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -17,4 +17,3 @@
include $(BUILD_PACKAGE)
-include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index efabbbf..3186add 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"እንግሊዝኛ (አሜሪካ)፣ የሥራሰው ቅጥ"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string>
diff --git a/packages/InputDevices/res/values-bn-rBD/strings.xml b/packages/InputDevices/res/values-bn-rBD/strings.xml
index bfbf3d3..a0ce313 100644
--- a/packages/InputDevices/res/values-bn-rBD/strings.xml
+++ b/packages/InputDevices/res/values-bn-rBD/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ইংরেজি (US), আন্তর্জাতিক শৈলী"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ইংরেজি (US), কোলেম্যাক শৈলী"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ইংরেজি (US), ডিভোরাক শৈলী"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ইংরেজি (US), ওয়ার্কম্যান শৈলী"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"জার্মান"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ফরাসী"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ফরাসী (কানাডা)"</string>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 614ba56..3e7b9c8 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglické (USA), styl Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index ce25623..6af0030 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Englisch (USA), Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 0a99dbb..da6dca2 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Αγγλικά (ΗΠΑ), στυλ Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 20d677b..6acf07b 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EE. UU.), Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index a05d071..f2848a9 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بینالمللی"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگلیسی (ایالات متحده)، سبک Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگلیسی (ایالات متحده)، سبک Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"انگلیسی (ایالات متحده)، سبک ورکمن"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)"</string>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 250333c..284efc8 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"englanti (Yhdysvallat), Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 3842584..b26a0ea 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index 679dba9..a428a23 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-gl-rES/strings.xml b/packages/InputDevices/res/values-gl-rES/strings.xml
index ab76a14..bb0f2a0 100644
--- a/packages/InputDevices/res/values-gl-rES/strings.xml
+++ b/packages/InputDevices/res/values-gl-rES/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EUA), estilo internacional"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EUA), estilo Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EUA), estilo Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EUA), estilo Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-gu-rIN/strings.xml b/packages/InputDevices/res/values-gu-rIN/strings.xml
index 63c3874..e83b0ca 100644
--- a/packages/InputDevices/res/values-gu-rIN/strings.xml
+++ b/packages/InputDevices/res/values-gu-rIN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"અંગ્રેજી (યુએસ), આંતરરાષ્ટ્રીય શૈલી"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"અંગ્રેજી (યુએસ), કોલેમેક શૈલી"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"અંગ્રેજી (યુએસ), ડ્વોરક શૈલી"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"અંગ્રેજી (યુએસ), વર્કમૅન શૈલી"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"જર્મન"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ફ્રેન્ચ"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ફ્રેન્ચ (કેનેડા)"</string>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index 829f817..27066ad 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleska (SAD), raspored Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
index f47aba2..0d11645 100644
--- a/packages/InputDevices/res/values-hy-rAM/strings.xml
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Անգլերեն (ԱՄՆ), Workman տեսակ"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string>
diff --git a/packages/InputDevices/res/values-is-rIS/strings.xml b/packages/InputDevices/res/values-is-rIS/strings.xml
index 35a5a17..de91275 100644
--- a/packages/InputDevices/res/values-is-rIS/strings.xml
+++ b/packages/InputDevices/res/values-is-rIS/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Enskt (Bandaríkin), alþjóðlegt"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Enskt (Bandaríkin), Colemak-stíll"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Enskt (Bandaríkin), Dvorak-stíll"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Enska (Bandaríkin), Workman-stíll"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Þýskt"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 0c8b4e9..b3bd576 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"אנגלית (ארה\"ב), סגנון Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"אנגלית (ארה\"ב), סגנון Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"אנגלית (ארה\"ב), סגנון Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index 7fbddd7..2b3daf5 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英語(アメリカ)、Workman配列"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
index 56bd3e3..66d147e 100644
--- a/packages/InputDevices/res/values-ka-rGE/strings.xml
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ინგლისური (აშშ), Workman სტილი"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string>
diff --git a/packages/InputDevices/res/values-kk-rKZ/strings.xml b/packages/InputDevices/res/values-kk-rKZ/strings.xml
index df2da88..d253542 100644
--- a/packages/InputDevices/res/values-kk-rKZ/strings.xml
+++ b/packages/InputDevices/res/values-kk-rKZ/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ағылшын (АҚШ), Халықаралық стилі"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ағылшын (АҚШ), Colemak стилі"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ағылшын (АҚШ), Dvorak стилі"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ағылшын (АҚШ), Workman стилі"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Неміс"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-kn-rIN/strings.xml b/packages/InputDevices/res/values-kn-rIN/strings.xml
index f88076c..243e6597 100644
--- a/packages/InputDevices/res/values-kn-rIN/strings.xml
+++ b/packages/InputDevices/res/values-kn-rIN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ಇಂಗ್ಲಿಷ್ (US), ಅಂತರರಾಷ್ಟ್ರೀಯ ಶೈಲಿ"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ಇಂಗ್ಲಿಷ್ (US), ಕೋಲ್ಮಾರ್ಕ್ ಶೈಲಿ"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ಇಂಗ್ಲಿಷ್ (US), ಡಿವೊರಾಕ್ ಶೈಲಿ"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ಇಂಗ್ಲೀಷ್ (US), ವರ್ಕ್ಮನ್ ಶೈಲಿ"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ಜರ್ಮನ್"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ಫ್ರೆಂಚ್"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ಫ್ರೆಂಚ್ (ಕೆನಡಾ)"</string>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index d215375..7758210 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"영어(미국), 워크맨 스타일"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string>
diff --git a/packages/InputDevices/res/values-ky-rKG/strings.xml b/packages/InputDevices/res/values-ky-rKG/strings.xml
index ee9cc5a..aa74733 100644
--- a/packages/InputDevices/res/values-ky-rKG/strings.xml
+++ b/packages/InputDevices/res/values-ky-rKG/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англис (АКШ), эл аралык"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англис (АКШ), Colemak стили"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англис (АКШ), Dvorak стили"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англисче (АКШ), Workman стили"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Немис"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
index 33c9f61..05b1b83 100644
--- a/packages/InputDevices/res/values-lo-rLA/strings.xml
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ອັງກິດ (ສະຫະລັດ), ແບບຄົນເຮັດວຽກ"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string>
diff --git a/packages/InputDevices/res/values-ml-rIN/strings.xml b/packages/InputDevices/res/values-ml-rIN/strings.xml
index b42bdb7..0faa40e 100644
--- a/packages/InputDevices/res/values-ml-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ml-rIN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ഇംഗ്ലീഷ് (യു.എസ്), അന്തർദ്ദേശീയ ശൈലി"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ഇംഗ്ലീഷ് (യു.എസ്), കോൽമാക് ശൈലി"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ഇംഗ്ലീഷ് (യു.എസ്), ദ്വരോക്ക് ശൈലി"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ഇംഗ്ലീഷ് (യുഎസ്), വർക്ക്മാൻ ശൈലി"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ജര്മ്മന്"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ഫ്രഞ്ച്"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ഫ്രഞ്ച് (കാനഡ)"</string>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index 378cdc1..37604e3 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelsk (USA), workman-stil"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ne-rNP/strings.xml b/packages/InputDevices/res/values-ne-rNP/strings.xml
index 571d717..4c3dec3 100644
--- a/packages/InputDevices/res/values-ne-rNP/strings.xml
+++ b/packages/InputDevices/res/values-ne-rNP/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अङ्ग्रेजी (अमेरिकी), अन्तर्राष्ट्रिय शैली"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अङ्ग्रेजी (अमेरिकी), कोलमाक शैली"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अङ्ग्रेजी (अमेरिकी), डेभोर्याक शैली"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"अंग्रेजी (अमेरिका), वर्कम्यान शैली"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रान्सेली"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (क्यानाडा)"</string>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 7175705..2ae815e32 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Angielski (USA), Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index 15c2d37..a1503a4 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index 15c2d37..a1503a4 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index ccfe568..795e9a2 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleză (SUA), stil Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 7d3038e..ac4c81b 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"английский (США, Workman)"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index f9e4610..a643c8e 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"angleška (ZDA), slog Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index a216bc3..b06f6fc 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"енглеска (САД), Workman стил"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index 23f2c98..89cb54e 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelskt (USA), workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index 720d427..1f447b0 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Kiingereza (US), mtindo wa Workman"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ta-rIN/strings.xml b/packages/InputDevices/res/values-ta-rIN/strings.xml
index 43d0bd1..32efe7b 100644
--- a/packages/InputDevices/res/values-ta-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ta-rIN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ஆங்கிலம் (யூஎஸ்), சர்வதேச நடை"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ஆங்கிலம் (யூஎஸ்), கோல்மாக் நடை"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ஆங்கிலம் (யூஎஸ்), டிவாரக் நடை"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ஆங்கிலம் (யூஎஸ்), வொர்க்மென் நடை"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"ஜெர்மன்"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ஃபிரெஞ்ச்"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ஃபிரெஞ்ச் (கனடா)"</string>
diff --git a/packages/InputDevices/res/values-te-rIN/strings.xml b/packages/InputDevices/res/values-te-rIN/strings.xml
index d214ae6..e07d4c8 100644
--- a/packages/InputDevices/res/values-te-rIN/strings.xml
+++ b/packages/InputDevices/res/values-te-rIN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ఇంగ్లీష్ (US), అంతర్జాతీయ శైలి"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ఇంగ్లీష్ (US), కొల్మాక్ శైలి"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ఇంగ్లీష్ (US), ద్వోరక్ శైలి"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ఆంగ్లం (US), వర్క్మాన్ శైలి"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"జర్మన్"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"ఫ్రెంచ్"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ఫ్రెంచ్ (కెనడా)"</string>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index 3dd0d3b..a8d9a0f 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"İngilizce (ABD), Workman stili"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ur-rPK/strings.xml b/packages/InputDevices/res/values-ur-rPK/strings.xml
index 2c5a8b8..3d2f618 100644
--- a/packages/InputDevices/res/values-ur-rPK/strings.xml
+++ b/packages/InputDevices/res/values-ur-rPK/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگریزی (امریکہ)، انٹرنیشنل سٹائل"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگریزی (امریکہ)، کول مارک سٹائل"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگریزی (امریکہ)، ڈوراک سٹائل"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"انگریزی (US)، ورک مین اسٹائل"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"جرمن"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسیسی"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسیسی (کینیڈا)"</string>
diff --git a/packages/InputDevices/res/values-uz-rUZ/strings.xml b/packages/InputDevices/res/values-uz-rUZ/strings.xml
index ee20328..9c55615 100644
--- a/packages/InputDevices/res/values-uz-rUZ/strings.xml
+++ b/packages/InputDevices/res/values-uz-rUZ/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglizcha (AQSH), xalqaro uslubda"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglizcha (AQSH), Kolemak uslubida"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglizcha (AQSH), Dvorak uslubida"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ingliz (AQSH), ishchi uslubda"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Nemischa"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransuzcha"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransuzcha (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index 2ba2667..c61dccb 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英语(美国),Workman 风格"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 80899d0..0dcffb0 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -8,8 +8,7 @@
<string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string>
<string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string>
<string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string>
- <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
- <skip />
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"I-English (US), isitayela sokusebenza"</string>
<string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string>
<string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string>
<string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 11e937b..df4a9f0 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -2,6 +2,7 @@
import android.content.ContentValues;
import android.content.Context;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -19,8 +20,15 @@
* Database for MTP objects.
* The object handle which is identifier for object in MTP protocol is not stable over sessions.
* When we resume the process, we need to remap our document ID with MTP's object handle.
- * The database object remembers the map of document ID and fullpath, and helps to remap object
- * handle and document ID by comparing fullpath.
+ *
+ * If the remote MTP device is backed by typical file system, the file name
+ * is unique among files in a directory. However, MTP protocol itself does
+ * not guarantee the uniqueness of name so we cannot use fullpath as ID.
+ *
+ * Instead of fullpath, we use artificial ID generated by MtpDatabase itself. The database object
+ * remembers the map of document ID and object handle, and remaps new object handle with document ID
+ * by comparing the directory structure and object name.
+ *
* TODO: Remove @VisibleForTesting annotation when we start to use this class.
*/
@VisibleForTesting
@@ -30,10 +38,9 @@
private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments";
- static final String COLUMN_DEVICE_ID = "deviceId";
- static final String COLUMN_STORAGE_ID = "storageId";
- static final String COLUMN_OBJECT_HANDLE = "objectHandle";
- static final String COLUMN_FULL_PATH = "fullPath";
+ static final String COLUMN_DEVICE_ID = "device_id";
+ static final String COLUMN_STORAGE_ID = "storage_id";
+ static final String COLUMN_OBJECT_HANDLE = "object_handle";
private static class OpenHelper extends SQLiteOpenHelper {
private static final String CREATE_TABLE_QUERY =
@@ -43,7 +50,6 @@
COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
COLUMN_STORAGE_ID + " INTEGER NOT NULL," +
COLUMN_OBJECT_HANDLE + " INTEGER," +
- COLUMN_FULL_PATH + " TEXT NOT NULL," +
DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," +
DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," +
@@ -86,17 +92,15 @@
}
@VisibleForTesting
- void putRootDocument(MtpRoot root) throws Exception {
+ void putRootDocument(Resources resources, MtpRoot root) throws Exception {
database.beginTransaction();
try {
final ContentValues values = new ContentValues();
values.put(COLUMN_DEVICE_ID, root.mDeviceId);
values.put(COLUMN_STORAGE_ID, root.mStorageId);
values.putNull(COLUMN_OBJECT_HANDLE);
- values.put(
- COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription));
values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
- values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
+ values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
values.putNull(Document.COLUMN_SUMMARY);
values.putNull(Document.COLUMN_LAST_MODIFIED);
values.putNull(Document.COLUMN_ICON);
@@ -113,7 +117,7 @@
}
@VisibleForTesting
- void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception {
+ void putDocument(int deviceId, MtpObjectInfo info) throws Exception {
database.beginTransaction();
try {
final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
@@ -134,9 +138,7 @@
values.put(COLUMN_DEVICE_ID, deviceId);
values.put(COLUMN_STORAGE_ID, info.getStorageId());
values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
- values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName()));
- values.put(
- Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat()));
+ values.put(Document.COLUMN_MIME_TYPE, mimeType);
values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
values.putNull(Document.COLUMN_SUMMARY);
values.put(
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7ce32542..8e1335f 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -1,6 +1,5 @@
package com.android.mtp;
-
import android.database.Cursor;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
@@ -15,7 +14,6 @@
MtpDatabase.COLUMN_DEVICE_ID,
MtpDatabase.COLUMN_STORAGE_ID,
MtpDatabase.COLUMN_OBJECT_HANDLE,
- MtpDatabase.COLUMN_FULL_PATH,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_SUMMARY,
@@ -25,6 +23,8 @@
DocumentsContract.Document.COLUMN_SIZE
};
+ private final TestResources resources = new TestResources();
+
@Override
public void tearDown() {
MtpDatabase.deleteDatabase(getContext());
@@ -32,35 +32,10 @@
public void testPutRootDocument() throws Exception {
final MtpDatabase database = new MtpDatabase(getContext());
- final MtpRoot root = new MtpRoot(
- 0,
- 1,
- "Device A",
- "Storage",
- 1000,
- 2000,
- "");
- database.putRootDocument(root);
-
- final MtpRoot duplicatedNameRoot = new MtpRoot(
- 0,
- 2,
- "Device A",
- "Storage",
- 1000,
- 2000,
- "");
- database.putRootDocument(duplicatedNameRoot);
-
- final MtpRoot strangeNameRoot = new MtpRoot(
- 0,
- 3,
- "Device A",
- "/@#%&<>Storage",
- 1000,
- 2000,
- "");
- database.putRootDocument(strangeNameRoot);
+ database.putRootDocument(resources, new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""));
+ database.putRootDocument(resources, new MtpRoot(0, 2, "Device", "Storage", 1000, 2000, ""));
+ database.putRootDocument(
+ resources, new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000, ""));
final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
assertEquals(3, cursor.getCount());
@@ -70,22 +45,23 @@
assertEquals("deviceId", 0, cursor.getInt(1));
assertEquals("storageId", 1, cursor.getInt(2));
assertTrue("objectHandle", cursor.isNull(3));
- assertEquals("fullPath", "/0/Storage", cursor.getString(4));
- assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5));
- assertEquals("displayName", "Storage", cursor.getString(6));
- assertTrue("summary", cursor.isNull(7));
- assertTrue("lastModified", cursor.isNull(8));
- assertTrue("icon", cursor.isNull(9));
- assertEquals("flag", 0, cursor.getInt(10));
- assertEquals("size", 1000, cursor.getInt(11));
+ assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4));
+ assertEquals("displayName", "Device Storage", cursor.getString(5));
+ assertTrue("summary", cursor.isNull(6));
+ assertTrue("lastModified", cursor.isNull(7));
+ assertTrue("icon", cursor.isNull(8));
+ assertEquals("flag", 0, cursor.getInt(9));
+ assertEquals("size", 1000, cursor.getInt(10));
cursor.moveToNext();
assertEquals("documentId", 2, cursor.getInt(0));
- assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+ assertEquals("displayName", "Device Storage", cursor.getString(5));
cursor.moveToNext();
assertEquals("documentId", 3, cursor.getInt(0));
- assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4));
+ assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5));
+
+ cursor.close();
}
public void testPutDocument() throws Exception {
@@ -96,7 +72,7 @@
builder.setStorageId(5);
builder.setFormat(MtpConstants.FORMAT_TEXT);
builder.setCompressedSize(1000);
- database.putDocument(0, "/0/Storage", builder.build());
+ database.putDocument(0, builder.build());
final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
assertEquals(1, cursor.getCount());
@@ -105,17 +81,16 @@
assertEquals("deviceId", 0, cursor.getInt(1));
assertEquals("storageId", 5, cursor.getInt(2));
assertEquals("objectHandle", 100, cursor.getInt(3));
- assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4));
- assertEquals("mimeType", "text/plain", cursor.getString(5));
- assertEquals("displayName", "test.txt", cursor.getString(6));
- assertTrue("summary", cursor.isNull(7));
- assertTrue("lastModified", cursor.isNull(8));
- assertTrue("icon", cursor.isNull(9));
+ assertEquals("mimeType", "text/plain", cursor.getString(4));
+ assertEquals("displayName", "test.txt", cursor.getString(5));
+ assertTrue("summary", cursor.isNull(6));
+ assertTrue("lastModified", cursor.isNull(7));
+ assertTrue("icon", cursor.isNull(8));
assertEquals(
"flag",
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
- cursor.getInt(10));
- assertEquals("size", 1000, cursor.getInt(11));
+ cursor.getInt(9));
+ assertEquals("size", 1000, cursor.getInt(10));
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 0c03814c..5765f0a 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -16,9 +16,6 @@
package com.android.mtp;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
@@ -26,7 +23,6 @@
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
-import android.test.mock.MockResources;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
@@ -39,16 +35,7 @@
private TestContentResolver mResolver;
private MtpDocumentsProvider mProvider;
private TestMtpManager mMtpManager;
- private final MockResources mResources = new MockResources() {
- @Override
- public String getString(int id) throws NotFoundException {
- switch (id) {
- case R.string.root_name:
- return "%1$s %2$s";
- }
- throw new NotFoundException();
- }
- };
+ private final TestResources mResources = new TestResources();
@Override
public void setUp() throws IOException {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
new file mode 100644
index 0000000..eb80e3b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.test.mock.MockResources;
+
+class TestResources extends MockResources {
+ @Override
+ public String getString(int id) throws NotFoundException {
+ switch (id) {
+ case R.string.root_name:
+ return "%1$s %2$s";
+ }
+ throw new NotFoundException();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
new file mode 100644
index 0000000..cf17ea5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.accessibility;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public class AccessibilityUtils {
+ public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
+
+ final static TextUtils.SimpleStringSplitter sStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+
+ /**
+ * @return the set of enabled accessibility services. If there are not services
+ * it returned the unmodifiable {@link Collections#emptySet()}.
+ */
+ public static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
+ final String enabledServicesSetting = Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (enabledServicesSetting == null) {
+ return Collections.emptySet();
+ }
+
+ final Set<ComponentName> enabledServices = new HashSet<ComponentName>();
+ final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
+ colonSplitter.setString(enabledServicesSetting);
+
+ while (colonSplitter.hasNext()) {
+ final String componentNameString = colonSplitter.next();
+ final ComponentName enabledService = ComponentName.unflattenFromString(
+ componentNameString);
+ if (enabledService != null) {
+ enabledServices.add(enabledService);
+ }
+ }
+
+ return enabledServices;
+ }
+
+ /**
+ * @return a localized version of the text resource specified by resId
+ */
+ public static CharSequence getTextForLocale(Context context, Locale locale, int resId) {
+ final Resources res = context.getResources();
+ final Configuration config = new Configuration(res.getConfiguration());
+ config.setLocale(locale);
+ final Context langContext = context.createConfigurationContext(config);
+ return langContext.getText(resId);
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index d83b516..0d1ad19 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -25,6 +25,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -41,14 +42,11 @@
import libcore.io.Streams;
import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.ArrayList;
@@ -107,11 +105,6 @@
*/
private void triggerLocalNotification(final Context context, final File bugreportFile,
final File screenshotFile) {
- // Files are kept on private storage, so turn into Uris that we can
- // grant temporary permissions for.
- final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
- final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
-
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
@@ -133,12 +126,22 @@
*/
private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ final String mimeType = "application/vnd.android.bugreport";
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setType("application/vnd.android.bugreport");
+ intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+ // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+ // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+ // create the ClipData object with the attachments URIs.
intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+ final ClipData clipData = new ClipData(
+ null, new String[] { mimeType },
+ new ClipData.Item(null, null, null, bugreportUri));
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ intent.setClipData(clipData);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5d622a0..be5c0fe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -214,6 +214,7 @@
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 6187070..cc35c51c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -35,18 +35,22 @@
<com.android.systemui.qs.QuickQSPanel
android:id="@+id/quick_qs_panel"
android:background="#0000"
- android:layout_width="142dp"
+ android:layout_width="144dp"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true" />
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="12dp" />
<LinearLayout
android:id="@+id/expanded_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:gravity="center"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
- android:layout_alignParentEnd="true">
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="10dp">
+
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
android:layout_width="48dp"
@@ -74,7 +78,8 @@
<ImageView
android:layout_width="48dp"
- android:layout_height="match_parent"
+ android:layout_height="48dp"
+ android:padding="12dp"
android:src="@drawable/ic_expand_less"
android:tint="@android:color/white" />
</LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 41ed64c..0e31d8d 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Vou uit"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Vou in"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skerm is vasgespeld"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Dit hou dit in sig totdat jy dit ontspeld. Raak en hou Terug en Oorsig op dieselfde tyd om te ontspeld."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dit hou dit in sig totdat jy ontspeld. Raak en hou Oorsig om te ontspeld."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Het dit"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, dankie"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Versteek <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 673710d..ec6ec93 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"አስፋ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ሰብስብ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ማያ ገጽ ተሰክቷል"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ይህ እስከሚነቅሉት ድረስ ድረስ በዕይታ ውስጥ እንዲቆይ ያደርገዋል። ለመንቀል በተመሳሳይ ጊዜ ተመለስን እና አጠቃላይ ዕይታን አንድ ላይ ነክተው ይያዙ።"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ይህ እስከሚነቅሉት ድረስ በዕይታ ውስጥ ያቆየዋል። እንዲነቀል ለማድረግ አጠቃላይ ዕይታን ነካ አድርገው ይያዙት።"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"ገባኝ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"አይ፣ አመሰግናለሁ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ይደበቅ?"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index c2c7e65..02fc3fe 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -397,8 +397,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"توسيع"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"تصغير"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"تم تثبيت الشاشة"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"رجوع\" و\"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"حسنًا"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"لا، شكرًا"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"هل تريد إخفاء <xliff:g id="TILE_LABEL">%1$s</xliff:g>؟"</string>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index a949dbbe8..aba22f8 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişləndirin"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yığcamlaşdırın"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrana sancaq taxıldı"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə eyni vaxtda toxunun və saxlayın."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə toxunun və saxlayın."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım!"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Yox, çox sağ olun"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlədilsin?"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index a59f4c5..50ac61f 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Разгъване"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свиване"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екранът е фиксиран"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за връщане назад и този за общ преглед едновременно."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за общ преглед."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Разбрах"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Не, благодаря"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Да се скрие ли „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index f4adc14..f32cfcc 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"প্রসারিত করুন"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"সঙ্কুচিত করুন"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"স্ক্রীন পিন করা হয়েছে"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে একই সময়ে ফিরুন এবং ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"বুঝেছি"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"না থাক"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> লুকাবেন?"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 00f96a7..429f70b 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Amplia"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Replega"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"La pantalla està fixada"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premuts els botons Enrere i Visió general a la vegada."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premut el botó Visió general."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"D\'acord"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, gràcies"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vols amagar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e429a06..ee77cf9 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -397,8 +397,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbalit"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sbalit"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je připnuta"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Chcete-li jej uvolnit, stiskněte a podržte současně tlačítka Zpět a Přehled."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Uvolníte jej stisknutím a podržením tlačítka Přehled."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Rozumím"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, děkuji"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skrýt <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index e4436c8..f749756 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Udvid"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skærmen er fastgjort"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Tilbage og Oversigt på samme tid, og hold dem nede for at frigøre denne skærm."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Oversigt, og hold den nede for at frigøre denne skærm."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK, det er forstået"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tak"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index b113083..cfaecaf 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Maximieren"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minimieren"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Bildschirm ist fixiert"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Zurück\" und \"Übersicht\" gleichzeitig, um die Fixierung aufzuheben."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Übersicht\", wenn Sie die Fixierung aufheben möchten."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nein danke"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ausblenden?"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 27a019c..6fc1b59 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ανάπτυξη"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Σύμπτυξη"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Η οθόνη καρφιτσώθηκε"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επιστροφή\" και \"Επισκόπηση\" ταυτόχρονα για ξεκαρφίτσωμα."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επισκόπηση\" για ξεκαρφίτσωμα."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Το κατάλαβα"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Όχι"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Απόκρυψη <xliff:g id="TILE_LABEL">%1$s</xliff:g>;"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index b1f71a2..ffdc3af7 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index bca484a..d4bb0a6 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fija"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionados los botones Atrás y Recientes al mismo tiempo para anular la fijación."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionado el botón Recientes para anular la fijación."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 3c89db8..d3cae62 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Mostrar"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ocultar"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fijada"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsados los botones de retroceso e información general."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsado el botón de información general."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 01e5848..0dfbdfc 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Laiendamine"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ahendamine"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekraan on kinnitatud"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ning hoidke korraga all nuppe Tagasi ja Ülevaade."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ja hoidke all nuppu Ülevaade."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Selge"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Tänan, ei"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Kas peita <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 613b44a..769164d 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Zabaldu"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tolestu"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantaila ainguratuta dago"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Horrela, ikusgai mantenduko da aingura kendu arte. Aingura kentzeko, eduki ukituta aldi berean \"Atzera\" eta \"Ikuspegi orokorra\" botoiak."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Horrela, ikusgai mantenduko da, aingura kendu arte. Aingura kentzeko, eduki ukituta \"Ikuspegi orokorra\" botoia."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ados"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ez, eskerrik asko"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ezkutatu nahi duzu?"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 9967225..6d9d43f 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -219,7 +219,7 @@
<string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"داده موقتاً متوقف شده است"</string>
<string name="data_usage_disabled_dialog" msgid="8453242888903772524">"چون به محدودیت داده تنظیم شده رسیدهاید، دستگاه مصرف داده را برای باقیمانده این دوره موقتاً متوقف کرده است.\n\nاگر ادامه دهید شاید موجب کسر هزینه از طرف شرکت مخابراتی شما شود."</string>
<string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"از سرگیری"</string>
- <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی وجود ندارد"</string>
+ <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی ندارید"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل شد"</string>
<string name="gps_notification_searching_text" msgid="8574247005642736060">"جستجو برای GPS"</string>
<string name="gps_notification_found_text" msgid="4619274244146446464">"مکان تنظیم شده توسط GPS"</string>
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"بزرگ کردن"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"کوچک کردن"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"صفحه نمایش پین شد"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، برگشت و نمای کلی را به صورت همزمان لمس کنید و نگهدارید."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، نمای کلی را لمس کنید و نگهدارید."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"متوجه شدم"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"نه سپاسگزارم"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> مخفی شود؟"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 2d138cb..79bf369 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Laajenna."</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tiivistä."</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Näyttö on kiinnitetty"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Edellinen- ja Viimeisimmät-kohtaa samanaikaisesti pitkään kiinnityksen poistamiseksi."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Viimeisimmät-kohtaa pitkään kiinnityksen poistamiseksi."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Selvä"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ei kiitos"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Piilotetaanko <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 979ee35..605e4c5 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"L\'écran est épinglé"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez un doigt simultanément sur « Retour » et « Aperçu »."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez le doigt sur « Aperçu »."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9ab6613..6beed78 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -327,8 +327,8 @@
<string name="interruption_level_priority_twoline" msgid="1564715335217164124">"Priorité\nuniquement"</string>
<string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"Alarmes\nuniquement"</string>
<string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charge en cours… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
- <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+ <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Changer d\'utilisateur (utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Écran épinglé"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez simultanément sur \"Retour\" et \"Aperçu\" de manière prolongée."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez de manière prolongée sur \"Aperçu\"."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g> ?"</string>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index bc44150..9a66d87 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ampliar"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A pantalla está fixada"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Atrás e Visión xeral ao mesmo tempo."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Visión xeral."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"De acordo"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Non, grazas"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Queres ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index ba8c517..fe60448 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"વિસ્તૃત કરો"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"સંકુચિત કરો"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"સ્ક્રીન પિન કરેલ છે"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે બેકને ટચ કરો અને પકડો અને તે જ સમયે વિહંગાવલોકન કરો."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે વિહંગાવલોકનને ટચ કરો અને પકડો."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"સમજાઈ ગયું"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"નહીં આભાર"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ને છુપાવીએ?"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 669ce5a..a433ca5 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करें"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त करें"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन कर दी गई है"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"इससे वह तब तक दृश्य में रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए वापस जाएं और अवलोकन करें को एक ही समय पर स्पर्श करके रखें."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"इससे वह तब तक दृश्य में बना रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए अवलोकन करें को स्पर्श करके रखें."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"समझ लिया"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"नहीं, रहने दें"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> को छिपाएं?"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index e928cbc..b72ac3d 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -394,8 +394,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširivanje"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sažimanje"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je prikvačen"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Istovremeno dodirnite i držite Natrag i Pregled da biste ga otkvačili."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Dodirnite i držite Pregled da biste ga otkvačili."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Shvaćam"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite li sakriti pločicu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index a347ede..d27813a 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Kibontás"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Összecsukás"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A képernyő rögzítve van"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Megjelenítve tartja addig, amíg Ön fel nem oldja fel a rögzítést. A rögzítés feloldásához tartsa egyszerre lenyomva a Vissza és az Áttekintés lehetőséget."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva az Áttekintés lehetőséget."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Értem"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nem, köszönöm"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Elrejti ezt: <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 18f6c88..125b19d 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ընդարձակել"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Կոծկել"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար միաժամանակ հպեք և պահեք Համատեսքի և Հետ կոճակները:"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Համատեսքի կոճակը:"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Հասկանալի է"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ոչ, շնորհակալություն"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Թաքցնե՞լ <xliff:g id="TILE_LABEL">%1$s</xliff:g>-ը:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index afcac63..1370ef40 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Luaskan"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ciutkan"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Layar dipasangi pin"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Kembali dan Ringkasan secara bersamaan untuk melepas pin."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Ringkasan untuk melepas pin."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Mengerti"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Lain kali"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 48d0d1a..a57e66c 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Stækka"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minnka"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skjárinn er festur"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Þetta heldur þessu opnu þangað til þú losar. Haltu bakk- og yfirlitshnöppunum inni á sama tíma til að losa."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Þetta heldur þessu opnu þangað til þú losar. Haltu yfirlitshnappinum inni til að losa."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ég skil"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nei, takk"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fela <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index dea0330..77be017 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Espandi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Comprimi"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"La schermata è bloccata"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"La schermata rimane visibile finché la sblocchi. Tocca e tieni premuti contemporaneamente Indietro e Panoramica per sbloccare."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La schermata rimane visibile finché la sblocchi. Tocca Panoramica e tieni premuto per sbloccare."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"No, grazie"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Nascondere <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 772a3fa..20cb1ea 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"הרחב"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"כווץ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"המסך מוצמד"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"הבנתי"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"לא, תודה"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"להסתיר<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 6e570e3..3d40c73 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"折りたたむ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"画面が固定されました"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"固定を解除するまで画面が常に表示されるようになります。[戻る]と[最近]を同時に押し続けると固定が解除されます。"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"固定を解除するまで画面が常に表示されるようになります。[最近]を押し続けると固定が解除されます。"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"はい"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"いいえ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>を非表示にしますか?"</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index 6f26123..8f42d5b 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"გავრცობა"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ჩაკეცვა"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ეკრანი ჩამაგრებულია"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „დაბრუნება“-ზე და „მიმოხილვა“-ზე ერთდროულად."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „მიმოხილვა“-ზე."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"გასაგებია"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"არა, გმადლობთ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"დაიმალოს <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 2668de4..192518a 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Жаю"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жию"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Экран түйрелді"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Кері» және «Шолу» түймелерін бір уақытта басып тұрыңыз."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Шолу» түймесін бір уақытта басып тұрыңыз."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Түсіндім"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Жоқ, рақмет"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жасыру керек пе?"</string>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 08d761f..284af53 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ពង្រីក"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"បង្រួម"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"អេក្រង់ត្រូវបានភ្ជាប់"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"រក្សាទុកវាក្នុងទិដ្ឋភាពរហូតដល់អ្នកផ្ដាច់។ ប៉ះ ហើយសង្កត់ថយក្រោយ និងទិដ្ឋភាពនៅពេលតែមួយដើម្បីផ្ដាច់។"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"វារក្សាទុកក្នុងទិដ្ឋភាពរហូតដល់អ្នកផ្ដាច់។ ប៉ះ និងសង្កត់ទិដ្ឋភាពដើម្បីផ្ដាច់។"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"យល់ហើយ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"ទេ អរគុណ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"លាក់ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 2cf1697..484551b 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ವಿಸ್ತರಿಸು"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ಸಂಕುಚಿಸು"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ಪರದೆಯನ್ನು ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ನೀವು ಅನ್ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್ಪಿನ್ ಮಾಡಲು ಒಂದೇ ಸಮಯದಲ್ಲಿ ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಒತ್ತಿ ಹಿಡಿದುಕೊಳ್ಳಿ ಹಾಗೂ ಅವಲೋಕಿಸಿ."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ನೀವು ಅನ್ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್ಪಿನ್ ಮಾಡಲು ಅವಲೋಕನವನ್ನು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"ತಿಳಿಯಿತು"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"ಧನ್ಯವಾದಗಳು"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ಮರೆಮಾಡುವುದೇ?"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 4e8be28..c7f5489 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"펼치기"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"접기"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"화면 고정됨"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 뒤로와 최근 사용을 동시에 길게 터치합니다."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 최근 사용을 길게 터치합니다."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"확인"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"거부"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>을(를) 숨기시겠습니까?"</string>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index fc6897f..f57c9fc 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Жайып көрсөтүү"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жыйнап коюу"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Экран кадалган"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, бир убакта Артка жана Карап чыгууну коё бербей басып туруңуз."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, Карап чыгууну коё бербей басып туруңуз."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Түшүндүм"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Жок, рахмат"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жашырылсынбы?"</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 6bb2043..18f3111 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ຂະຫຍາຍ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ຫຍໍ້ລົງ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ປັກໝຸດໜ້າຈໍແລ້ວ"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ມັນຈະຮັກສາໜ້າຈໍໄວ້ໃນມຸມມອງຂອງທ່ານຈົນກວ່າທ່ານຈະຖອດໝຸດ. ແຕະປຸ່ມ ກັບຄືນ ແລະ ພາບຮວມ ຄ້າງໄວ້ພ້ອມກັນເພື່ອຖອດໝຸດ."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ມັນຈະຮັກສາໜ້າຈໍໄວ້ໃນມຸມມອງຂອງທ່ານຈົນກວ່າທ່ານຈະຖອດໝຸດ. ແຕະປຸ່ມ ພາບຮວມ ຄ້າງໄວ້ເພື່ອຖອດໝຸດ."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"ເຂົ້າໃຈແລ້ວ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"ບໍ່, ຂອບໃຈ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ເຊື່ອງ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ຫຼືບໍ່?"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8883cb6..307b7d9 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Išskleisti"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sutraukti"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekranas prisegtas"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, tuo pačiu metu palieskite ir laikykite „Atgal“ ir „Apžvalga“."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, palieskite ir laikykite „Apžvalga“."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Supratau"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, ačiū"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Slėpti „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 47ca06a..3660cda 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -394,8 +394,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Izvērst"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sakļaut"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrāns ir piesprausts"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, vienlaikus pieskarieties vienumiem “Atpakaļ” un “Pārskats” un turiet tos nospiestus."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, pieskarieties vienumam “Pārskats” un turiet to nospiestu."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Sapratu!"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nē, paldies"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vai paslēpt vienumu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 891ffd8..f8d0417 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Собери"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екранот е прикачен"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ќе се гледа сè додека не го откачите. Допрете и држете Назад и Краток преглед истовремено за откачување."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ќе се гледа сè додека не го откачите. Допрете и држете Краток преглед за откачување."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Сфатив"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Не, фала"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сокриј <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 64d4184..516e846 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"വികസിപ്പിക്കുക"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ചുരുക്കുക"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"സ്ക്രീൻ പിൻ ചെയ്തു"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യാൻ \'മടങ്ങുക\', \'ചുരുക്കവിവരണം\' എന്നിവ ഒരേ സമയം സ്പർശിച്ച് പിടിക്കുക."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യുന്നതിന് \'ചുരുക്കവിവരണം\' സ്പർശിച്ചുപിടിക്കുക."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"മനസ്സിലായി"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"വേണ്ട, നന്ദി"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> എന്നത് മറയ്ക്കണോ?"</string>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 740ee0d..8cb8df1 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -391,8 +391,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Дэлгэх"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Хураах"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Дэлгэц эхэнд байрлуулагдсан"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хүрээд, Back дээр удаан дараад хаахдаа Overview-ийг дар"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хаахын тулд хүрээдOverview-ийг дар"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ойлголоо"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Үгүй"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>-ийг нуух уу?"</string>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 00d7bc8..cfa6b5f 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करा"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संकुचित करा"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन केलेली आहे"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी एकाच वेळी परत आणि अलीकडील ला स्पर्श करा आणि धरून ठेवा आणि विहंगावलोकन करा."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"समजले"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"नाही धन्यवाद"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> लपवायचे?"</string>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 10865dd..6ff9a61 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Kembangkan"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Runtuhkan"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skrin telah disemat"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Kembali dan Gambaran Keseluruhan pada masa yang sama untuk menyahsemat."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Gambaran Keseluruhan untuk menyahsemat."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Faham"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Tidak"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 5cb6087..e4b3e54 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"တိုးချဲ့ရန်"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ခေါက်သိမ်းရန်..."</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"မျက်နှာပြင် ပင်ထိုးပြီးပါပြီ"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် အနောက်နှင့် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"အဲဒါ ရပြီ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"မလို ကျေးဇူးပဲ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ဝှက်မည်လား?"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 39b404a..2882d7a8 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Utvid"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skjermen er låst"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt samtidig for å låse opp."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt for å låse opp."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Skjønner"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nei takk"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 2a312f3..19d84ce 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"लुकाउनुहुन्छ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
@@ -440,7 +440,7 @@
<string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
<string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
<string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
- <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्नुहुन्छ?"</string>
+ <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्ने हो?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
<string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"सक्रिय पार्नुहोस्"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 6bbb340..95ddeab 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Uitvouwen"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Samenvouwen"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Scherm is vastgezet"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Terug\' en \'Overzicht\' tegelijkertijd aangeraakt om het los te maken."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Overzicht\' aangeraakt om het los te maken."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ik snap het"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, bedankt"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> verbergen?"</string>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 61b68c1..6cb50b3 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ਨਸ਼ਟ ਕਰੋ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ਸਕ੍ਰੀਨ ਪਿੰਨ ਕੀਤੀ"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਪਿੱਛੇ ਅਤੇ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਇੱਕੋ ਸਮੇਂ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"ਸਮਝ ਗਿਆ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ਕੀ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ਨੂੰ ਲੁਕਾਉਣਾ ਹੈ?"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index caa3081..1821cdd 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozwiń"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zwiń"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran jest przypięty"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj jednocześnie Wstecz i Przegląd."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj Przegląd."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, dziękuję"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ukryć <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index b82eb32..68ecfc8 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c137908..61a76f9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Reduzir"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"O ecrã está fixado"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Será mantido na vista até soltar. Toque sem soltar em Anterior e Vista geral em simultâneo para soltar."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Será mantido na vista até soltar. Toque sem soltar em Vista geral para soltar."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Compreendi"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Pretende ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index b82eb32..68ecfc8 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b5819ea..3af273d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -255,7 +255,7 @@
<string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localizarea este dezactivată"</string>
<string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispozitiv media"</string>
<string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
- <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgenţă"</string>
+ <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgență"</string>
<string name="quick_settings_settings_label" msgid="5326556592578065401">"Setări"</string>
<string name="quick_settings_time_label" msgid="4635969182239736408">"Ora"</string>
<string name="quick_settings_user_label" msgid="5238995632130897840">"Eu"</string>
@@ -394,8 +394,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Extindeți"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Restrângeți"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ecranul este fixat"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Înapoi și pe Vizualizare generală simultan pentru a anula fixarea."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Vizualizare generală pentru a anula fixarea."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Am înțeles"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nu, mulțumesc"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ascundeți <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 0806684..296876c 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -397,8 +397,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Развернуть"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свернуть"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Блокировка в приложении включена"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Это приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки \"Назад\" и \"Обзор\"."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки Назад и Обзор."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"ОК"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Нет, спасибо"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Скрыть параметр \"<xliff:g id="TILE_LABEL">%1$s</xliff:g>\"?"</string>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index 39147ff..185fc05 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -393,8 +393,7 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"දිග හරින්න"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"හකුළන්න"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"තීරය අමුණන ලදි"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට ආපසු සහ දළ විශ්ලේෂණය එකම වේලාවක ස්පර්ෂ කර අල්ලා සිටින්න."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට දළ විශ්ලේෂණය ස්පර්ෂ කර අල්ලා සිටින්න."</string>
+ <string name="screen_pinning_description" msgid="3577937698406151604">"මෙය ඔබ ගලවන තෙක් එය දසුන තුළ තබයි. ගැලවීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string>
<string name="screen_pinning_positive" msgid="3783985798366751226">"හරි, තේරුණා"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"එපා ස්තූතියි"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> සඟවන්නද?"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 86c8fe2..ebfd035 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -397,8 +397,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbaliť"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zbaliť"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je pripnutá"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Ak ho chcete uvoľniť, stlačte a podržte súčasne tlačidlá Späť a Prehľad."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho stlačením a podržaním tlačidla Prehľad."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Dobre"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, vďaka"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skryť <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index c5dd188..d87b9a4 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Razširi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Strni"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je pripet"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Hkrati pridržite tipko za nazaj in tipko za pregled, če ga želite odpeti."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Pridržite tipko za pregled, če ga želite odpeti."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Razumem"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite skriti <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index fcd65f7..5519e6b 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Zgjeroje"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Mbylle"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrani u gozhdua"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Prapa\" dhe \"Përmbledhje\" për ta hequr nga gozhdimi."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Përmbledhje\" për ta hequr nga gozhdimi."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"E kuptova!"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Jo, faleminderit!"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Të fshihet <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2c55a5c..67959f5 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -394,8 +394,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Скупи"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екран је закачен"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"На овај начин ово остаје приказано док га не откачите. Истовремено додирните и задржите Назад и Преглед да бисте га откачили."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"На овај начин ово остаје приказано док га не откачите. Додирните и задржите Преглед да бисте га откачили."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Важи"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Не, хвала"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Желите ли да сакријете <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index e8b1765..48fe512 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Utöka"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Komprimera"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skärmen har fästs"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Med den här funktionen är skärmen synlig tills du lossar den. Tryck länge på Tillbaka och Översikt samtidigt om du vill lossa skärmen."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Detta visar skärmen tills du lossar den. Tryck länge på Översikt om du vill lossa skärmen."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tack"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vill du dölja <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index bbe271d..2b65540 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Panua"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Kunja"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skrini imebandikwa"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Hii itaendelea kuonyesha hadi ubandue. Gusa na ushikilie Nyuma na Muhtasari kwa wakati mmoja ili ubandue."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Hii itaendelea kuonyesha hadi uibandue. Gusa na ushikilie Muhtasari ili ubandue."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Nimeelewa"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Hapana, asante"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ungependa kuficha <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 08bef13..1f4a4a2 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"விரிவாக்கு"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"சுருக்கு"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"திரை பொருத்தப்பட்டது"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, பின் மற்றும் மேலோட்டப் பார்வையை ஒரே நேரத்தில் தொட்டுப் பிடிக்கவும்."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, மேலோட்டப் பார்வையைத் தொட்டுப் பிடிக்கவும்."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"புரிந்தது"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"வேண்டாம்"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ஐ மறைக்கவா?"</string>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index 46bc704..d0b3439 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"విస్తరింపజేయండి"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"కుదించండి"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"స్క్రీన్ పిన్ చేయబడింది"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"ఇది మీరు అన్పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్పిన్ చేయడానికి వెనుకకు మరియు స్థూలదృష్టిని ఒకేసారి తాకి, ఉంచండి."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ఇది మీరు అన్పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్పిన్ చేయడానికి స్థూలదృష్టిని తాకి, ఉంచండి."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"అర్థమైంది"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"వద్దు, ధన్యవాదాలు"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ని దాచాలా?"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f7e7be0..cb2921d 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ขยาย"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ยุบ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ตรึงหน้าจอแล้ว"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"กลับ\" และ \"ภาพรวม\" พร้อมกันค้างไว้เพื่อเลิกตรึง"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"ภาพรวม\" ค้างไว้เพื่อเลิกตรึง"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"รับทราบ"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"ไม่เป็นไร ขอบคุณ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ซ่อน <xliff:g id="TILE_LABEL">%1$s</xliff:g> ไหม"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 92a6cdc..43969e9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Palawakin"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"I-collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Naka-pin ang screen"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Bumalik at Pangkalahatang-ideya nang sabay upang mag-unpin."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Pangkalahatang-ideya upang mag-unpin."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Nakuha ko"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Hindi, salamat na lang"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Itago ang <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2b9d6da..7a044ef 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişlet"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Daralt"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran sabitlendi"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Geri ve Genel Bakış öğesine aynı anda dokunun ve basılı tutun."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Genel Bakış\'a dokunun ve basılı tutun."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Hayır, teşekkürler"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlensin mi?"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index c040439..37030ac 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Розгорнути"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Згорнути"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екран закріплено"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, одночасно натисніть і втримуйте кнопки \"Назад\" і \"Огляд\"."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, натисніть і втримуйте кнопку \"Огляд\"."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Зрозуміло"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Ні, дякую"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сховати <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index b0a5336..0712f98 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"پھیلائیں"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"سکیڑیں"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"اسکرین پن کردہ ہے"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے واپس اور عمومی جائزہ کو ایک ساتھ ٹچ کریں اور پکڑ کر رکھیں۔"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے عمومی جائزہ کو ٹچ کریں اور پکڑ کر رکھیں۔"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"سمجھ آ گئی"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"نہیں شکریہ"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> کو چھپائیں؟"</string>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 9b9e2cd..ad3dcc7 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Yoyish"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yig‘ish"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran qadaldi"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Orqaga” va “Umumiy nazar” tugmalarini bir vaqtda bosing va ushlab turing."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Umumiy nazar” tugmasini bosing va ushlab turing."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Yo‘q, kerakmas"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> berkitilsinmi?"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d4ed7a5..f13033b 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Mở rộng"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Thu gọn"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Màn hình được ghim"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại và Tổng quan cùng một lúc để bỏ ghim."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại để bỏ ghim."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ok"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Không, cảm ơn"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ẩn <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 05416ca..ade7636 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展开"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收起"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"已固定屏幕"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"这将会固定显示此屏幕,直到您取消固定为止。触摸并同时按住“返回”和“概览”即可取消固定屏幕。"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"这将会固定显示此屏幕,直到您取消固定为止。触摸并按住“概览”即可取消固定屏幕。"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"不用了"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"要隐藏“<xliff:g id="TILE_LABEL">%1$s</xliff:g>”吗?"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index ba4d3cb..aa6e065c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"在您取消固定前,它會保持在檢視狀態。輕觸並同時按住 [返回] 和 [概覽],即可取消固定。"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"在您取消固定前,它會保持在檢視狀態。輕觸並按住 [概覽] 即可取消固定。"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏 <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6e0c852..ffaaefe 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -395,8 +395,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。同時按住返回按鈕和總覽按鈕即可取消固定。"</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。按住總覽按鈕即可取消固定。"</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 147f6b6..f1df714 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -393,8 +393,8 @@
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Nweba"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Goqa"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Isikrini siphiniwe"</string>
- <string name="screen_pinning_description" msgid="1346522416878235405">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Emuva no-Ukubuka konke ngesikhathi esisodwa ukuze ususe ukuphina."</string>
- <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Ukubuka konke ukuze ususe ukuphina."</string>
+ <!-- no translation found for screen_pinning_description (3577937698406151604) -->
+ <skip />
<string name="screen_pinning_positive" msgid="3783985798366751226">"Ngiyitholile"</string>
<string name="screen_pinning_negative" msgid="3741602308343880268">"Cha ngiyabonga"</string>
<string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fihla i-<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bae8017..0c638a2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -136,7 +136,7 @@
<integer name="touch_acceptance_delay">700</integer>
<!-- The duration in seconds to wait before the dismiss buttons are shown. -->
- <integer name="recents_task_bar_dismiss_delay_seconds">1</integer>
+ <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
<!-- The min animation duration for animating views that are currently visible. -->
<integer name="recents_filter_animate_current_views_duration">250</integer>
@@ -269,9 +269,6 @@
<!-- Duration of the expansion animation in the volume dialog -->
<item name="volume_expand_animation_duration" type="integer">300</item>
- <!-- Whether to show a "shelf" of apps at the bottom of the screen. -->
- <bool name="config_enableAppShelf">false</bool>
-
<!-- Whether to show the full screen user switcher. -->
<bool name="config_enableFullscreenUserSwitcher">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index cbc92f2..a4137b9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -128,6 +128,8 @@
<dimen name="qs_new_tile_height">100dp</dimen>
<dimen name="qs_quick_actions_height">88dp</dimen>
<dimen name="qs_quick_actions_padding">25dp</dimen>
+ <dimen name="qs_quick_tile_size">48dp</dimen>
+ <dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
@@ -230,6 +232,9 @@
<!-- The size of the lock-to-app button icon. -->
<dimen name="recents_lock_to_app_icon_size">28dp</dimen>
+ <!-- The amount to allow the stack to overscroll. -->
+ <dimen name="recents_stack_overscroll">24dp</dimen>
+
<!-- Space reserved for the cards behind the top card in the top stack -->
<dimen name="top_stack_peek_amount">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3a5680d..7c86f96 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -999,9 +999,7 @@
<!-- Screen pinning dialog title. -->
<string name="screen_pinning_title">Screen is pinned</string>
<!-- Screen pinning dialog description. -->
- <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin.</string>
- <!-- Screen pinning dialog description when in accessibility mode. -->
- <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch and hold Overview to unpin.</string>
+ <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back to unpin.</string>
<!-- Screen pinning positive response. -->
<string name="screen_pinning_positive">Got it</string>
<!-- Screen pinning negative response. -->
diff --git a/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java
new file mode 100644
index 0000000..3362650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
+
+public class QSQuickTileView extends QSTileBaseView {
+
+ private final int mPadding;
+ private final ImageView mIcon;
+
+ public QSQuickTileView(Context context) {
+ super(context);
+ mPadding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
+ mIcon = createIcon();
+ addView(mIcon);
+ }
+
+ protected ImageView createIcon() {
+ final ImageView icon = new ImageView(mContext);
+ icon.setId(android.R.id.icon);
+ icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ return icon;
+ }
+
+ @Override
+ public void init(OnClickListener click, OnClickListener clickSecondary,
+ OnLongClickListener longClick) {
+ setClickable(true);
+ setOnClickListener(click);
+ }
+
+ @Override
+ protected void handleStateChanged(QSTile.State state) {
+ mIcon.setImageDrawable(state.icon.getDrawable(getContext()));
+ setContentDescription(state.contentDescription);
+ }
+
+ @Override
+ public boolean setType(int type) {
+ return false;
+ }
+
+ @Override
+ public View updateAccessibilityOrder(View previousView) {
+ return this;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mIcon.measure(exactly(getMeasuredWidth() - 2 * mPadding),
+ exactly(getMeasuredHeight() - 2 * mPadding));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ layout(mIcon, mPadding, mPadding);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 1d0bfe7..9a4cd93 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -27,4 +27,9 @@
void cancelPreloadingRecents();
void showNextAffiliatedTask();
void showPrevAffiliatedTask();
+
+ /**
+ * Docks the top-most task and opens recents.
+ */
+ void dockTopTask();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c6bcfc4..794e900 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -566,7 +566,7 @@
public static final class TileRecord extends Record {
public QSTile<?> tile;
- public QSTileView tileView;
+ public QSTileBaseView tileView;
public int row;
public int col;
public boolean scanState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 61cb224..5d928d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -98,7 +98,7 @@
return mHost;
}
- public QSTileView createTileView(Context context) {
+ public QSTileBaseView createTileView(Context context) {
return new QSTileView(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
new file mode 100644
index 0000000..72fc88d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public abstract class QSTileBaseView extends ViewGroup {
+
+ public static final int QS_TYPE_NORMAL = 0;
+ public static final int QS_TYPE_DUAL = 1;
+ public static final int QS_TYPE_QUICK = 2;
+
+ private final H mHandler = new H();
+
+ public QSTileBaseView(Context context) {
+ super(context);
+ }
+
+ public QSTileBaseView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+
+ public void onStateChanged(QSTile.State state) {
+ mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
+ }
+
+ public abstract void init(OnClickListener click, OnClickListener clickSecondary,
+ OnLongClickListener longClick);
+ public abstract View updateAccessibilityOrder(View previousView);
+ public abstract boolean setType(int type);
+
+ protected abstract void handleStateChanged(QSTile.State state);
+
+ protected static int exactly(int size) {
+ return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ }
+
+ protected static void layout(View child, int left, int top) {
+ child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
+ }
+
+ private class H extends Handler {
+ private static final int STATE_CHANGED = 1;
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == STATE_CHANGED) {
+ handleStateChanged((QSTile.State) msg.obj);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index d914beb..cc264a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -44,18 +44,13 @@
import java.util.Objects;
/** View that represents a standard quick settings tile. **/
-public class QSTileView extends ViewGroup {
+public class QSTileView extends QSTileBaseView {
private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
Typeface.NORMAL);
- public static final int QS_TYPE_NORMAL = 0;
- public static final int QS_TYPE_DUAL = 1;
- public static final int QS_TYPE_QUICK = 2;
-
protected final Context mContext;
private final View mIcon;
private final View mDivider;
- private final H mHandler = new H();
private final int mIconSizePx;
private final int mTileSpacingPx;
private int mTilePaddingTopPx;
@@ -291,10 +286,6 @@
return MeasureSpec.EXACTLY;
}
- private static int exactly(int size) {
- return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
- }
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getMeasuredWidth();
@@ -334,10 +325,6 @@
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
- private static void layout(View child, int left, int top) {
- child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
- }
-
protected void handleStateChanged(QSTile.State state) {
if (mIcon instanceof ImageView) {
setIcon((ImageView) mIcon, state);
@@ -369,10 +356,6 @@
}
}
- public void onStateChanged(QSTile.State state) {
- mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
- }
-
/**
* Update the accessibility order for this view.
*
@@ -392,17 +375,4 @@
firstView.setAccessibilityTraversalAfter(previousView.getId());
return lastView;
}
-
- private class H extends Handler {
- private static final int STATE_CHANGED = 1;
- public H() {
- super(Looper.getMainLooper());
- }
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == STATE_CHANGED) {
- handleStateChanged((State) msg.obj);
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index a2d9ef0..6a053be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -58,6 +60,7 @@
ArrayList<QSTile<?>> quickTiles = new ArrayList<>();
for (QSTile<?> tile : tiles) {
if (tile.getTileType() == QSTileView.QS_TYPE_QUICK) {
+ Log.d("QSPanel", "Adding " + tile.getTileSpec());
quickTiles.add(tile);
}
if (quickTiles.size() == 2) {
@@ -71,16 +74,17 @@
public HeaderTileLayout(Context context) {
super(context);
+ setGravity(Gravity.CENTER_VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- int qsCompensation = (int)
- context.getResources().getDimension(R.dimen.qs_header_neg_padding);
- setPadding(0, qsCompensation, 0, 0);
+
+ int padding =
+ mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
ImageView downArrow = new ImageView(context);
downArrow.setImageResource(R.drawable.ic_expand_more);
downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
android.R.color.white, null)));
downArrow.setLayoutParams(generateLayoutParams());
- downArrow.setPadding(0, -qsCompensation, 0, 0);
+ downArrow.setPadding(padding, padding, padding, padding);
addView(downArrow);
setOrientation(LinearLayout.HORIZONTAL);
}
@@ -95,7 +99,9 @@
}
private LayoutParams generateLayoutParams() {
- LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT);
+ int size =
+ mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
+ LayoutParams lp = new LayoutParams(0, size);
lp.weight = 1;
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index f3ad9d8..e2d2ffb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -27,13 +27,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
/** Quick settings tile: Cellular **/
@@ -74,7 +73,7 @@
}
@Override
- public QSTileView createTileView(Context context) {
+ public QSTileBaseView createTileView(Context context) {
return new SignalTileView(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
index 13ccaaa..b77191e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java
@@ -16,9 +16,12 @@
package com.android.systemui.qs.tiles;
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
-/** Quick settings tile: Wifi **/
+/** Quick settings tile: Airplane mode **/
public class QAirplaneTile extends AirplaneModeTile {
public QAirplaneTile(Host host) {
@@ -26,6 +29,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
index 02975cb..4fe7e45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java
@@ -16,6 +16,9 @@
package com.android.systemui.qs.tiles;
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
/** Quick settings tile: Bluetooth **/
@@ -26,6 +29,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
index 31035cd..e115755e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java
@@ -16,9 +16,12 @@
package com.android.systemui.qs.tiles;
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
-/** Quick settings tile: Wifi **/
+/** Quick settings tile: Flashlight **/
public class QFlashlightTile extends FlashlightTile {
public QFlashlightTile(Host host) {
@@ -26,6 +29,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
index 3675f02..8b3013a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
@@ -15,11 +15,14 @@
*/
package com.android.systemui.qs.tiles;
+import android.content.Context;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.R;
public class QLockTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback {
@@ -31,6 +34,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
index e066bab..5f5cab5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java
@@ -16,6 +16,9 @@
package com.android.systemui.qs.tiles;
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
/** Quick settings tile: Rotation **/
@@ -26,6 +29,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
index b157275..f0fe87d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java
@@ -16,6 +16,9 @@
package com.android.systemui.qs.tiles;
+import android.content.Context;
+import com.android.systemui.QSQuickTileView;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.statusbar.policy.WifiIcons;
@@ -27,6 +30,11 @@
}
@Override
+ public QSTileBaseView createTileView(Context context) {
+ return new QSQuickTileView(context);
+ }
+
+ @Override
public int getTileType() {
return QSTileView.QS_TYPE_QUICK;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 91e0218..3763618 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -30,6 +30,7 @@
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -94,7 +95,7 @@
}
@Override
- public QSTileView createTileView(Context context) {
+ public QSTileBaseView createTileView(Context context) {
return new SignalTileView(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index cdb6b93..ebfacac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -37,6 +37,8 @@
public static final boolean EnableTaskFiltering = false;
// Enables dismiss-all
public static final boolean EnableDismissAll = false;
+ // Enables fast-toggling
+ public static final boolean EnableFastToggleRecents = false;
// Enables the thumbnail alpha on the front-most task
public static final boolean EnableThumbnailAlphaOnFrontmost = false;
// This disables the search bar integration
@@ -62,8 +64,6 @@
}
public static class TaskStackView {
- public static final int TaskStackMinOverscrollRange = 32;
- public static final int TaskStackMaxOverscrollRange = 128;
public static final int FilterStartDelay = 25;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index c216f97..4d40cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -54,6 +54,7 @@
private static SystemServicesProxy sSystemServicesProxy;
private static RecentsTaskLoader sTaskLoader;
+ private static RecentsConfiguration sConfiguration;
private Handler mHandler;
private RecentsImpl mImpl;
@@ -129,10 +130,15 @@
return sSystemServicesProxy;
}
+ public static RecentsConfiguration getConfiguration() {
+ return sConfiguration;
+ }
+
@Override
public void start() {
sSystemServicesProxy = new SystemServicesProxy(mContext);
sTaskLoader = new RecentsTaskLoader(mContext);
+ sConfiguration = new RecentsConfiguration(mContext);
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
@@ -284,6 +290,11 @@
}
@Override
+ public void dockTopTask() {
+ mImpl.dockTopTask();
+ }
+
+ @Override
public void showNextAffiliatedTask() {
mImpl.showNextAffiliatedTask();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c416967..dc802595d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -32,6 +32,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
@@ -41,16 +42,21 @@
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsPackageMonitor;
@@ -69,9 +75,11 @@
*/
public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
+ private final static String TAG = "RecentsActivity";
+ private final static boolean DEBUG = false;
+
public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
- RecentsConfiguration mConfig;
RecentsPackageMonitor mPackageMonitor;
long mLastTabKeyEventTime;
boolean mFinishedOnStartup;
@@ -96,6 +104,14 @@
// Runnable to be executed after we paused ourselves
Runnable mAfterPauseRunnable;
+ // The trigger to automatically launch the current task
+ DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+ @Override
+ public void run() {
+ boolean dismissed = dismissRecentsToFocusedTask(false);
+ }
+ });
+
/**
* A common Runnable to finish Recents either by calling finish() (with a custom animation) or
* launching Home with some ActivityOptions. Generally we always launch home when we exit
@@ -157,7 +173,8 @@
}
// Start loading tasks according to the load plan
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
if (!plan.hasTasks()) {
loader.preloadTasks(plan, launchState.launchedFromHome);
}
@@ -244,9 +261,27 @@
MetricsLogger.histogram(this, "overview_task_count", taskCount);
}
- /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
+ /**
+ * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+ */
+ boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+ // If we currently have filtered stacks, then unfilter those first
+ if (checkFilteredStackState &&
+ mRecentsView.unfilterFilteredStacks()) return true;
+ // If we have a focused Task, launch that Task now
+ if (mRecentsView.launchFocusedTask()) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismisses recents if we are already visible and the intent is to toggle the recents view.
+ */
boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// If we currently have filtered stacks, then unfilter those first
@@ -318,7 +353,6 @@
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the widget host (the host id is static and does not change)
- mConfig = RecentsConfiguration.getInstance();
if (!Constants.DebugFlags.App.DisableSearchBar) {
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
}
@@ -365,7 +399,8 @@
// If this is a new instance from a configuration change, then we have to manually trigger
// the enter animation state, or if recents was relaunched by AM, without going through
// the normal mechanisms
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
boolean wasLaunchedByAm = !launchState.launchedFromHome &&
!launchState.launchedFromAppWithThumbnail;
if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
@@ -390,6 +425,11 @@
mRecentsView.post(mAfterPauseRunnable);
mAfterPauseRunnable = null;
}
+
+ if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ // Stop the fast-toggle dozer
+ mIterateTrigger.stopDozing();
+ }
}
@Override
@@ -403,7 +443,8 @@
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
// through SystemUI, we need to reset the config launch flags to ensure that we do not
// wait on the system to send a signal that was never queued.
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
launchState.launchedFromHome = false;
launchState.launchedFromSearchHome = false;
launchState.launchedFromAppWithThumbnail = false;
@@ -467,22 +508,27 @@
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
- mRecentsView.focusNextTask(!backward);
+ if (backward) {
+ EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
+ } else {
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
+ }
mLastTabKeyEventTime = SystemClock.elapsedRealtime();
}
return true;
}
case KeyEvent.KEYCODE_DPAD_UP: {
- mRecentsView.focusNextTask(true);
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
return true;
}
case KeyEvent.KEYCODE_DPAD_DOWN: {
- mRecentsView.focusNextTask(false);
+ EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
return true;
}
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_FORWARD_DEL: {
- mRecentsView.dismissFocusedTask();
+ EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
+
// Keep track of deletions by keyboard
MetricsLogger.histogram(this, "overview_task_dismissed_source",
Constants.Metrics.DismissSourceKeyboard);
@@ -542,6 +588,16 @@
dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
}
+ public final void onBusEvent(IterateRecentsEvent event) {
+ // Focus the next task
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
+ mIterateTrigger.poke();
+ }
+
+ public final void onBusEvent(UserInteractionEvent event) {
+ mIterateTrigger.stopDozing();
+ }
+
public final void onBusEvent(HideRecentsEvent event) {
if (event.triggeredFromAltTab) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -558,18 +614,35 @@
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
- mRecentsView.startEnterRecentsAnimation(ctx);
+ ctx.postAnimationTrigger.increment();
if (mSearchWidgetInfo != null) {
- ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- // Start listening for widget package changes if there is one bound
- if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
+ if (!Constants.DebugFlags.App.DisableSearchBar) {
+ ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ // Start listening for widget package changes if there is one bound
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
+ }
}
- }
- });
+ });
+ }
}
+ ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ // If we are not launching with alt-tab and fast-toggle is enabled, then start
+ // the dozer now
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ if (Constants.DebugFlags.App.EnableFastToggleRecents &&
+ !launchState.launchedWithAltTab) {
+ mIterateTrigger.startDozing();
+ }
+ }
+ });
+ mRecentsView.startEnterRecentsAnimation(ctx);
+ ctx.postAnimationTrigger.decrement();
}
public final void onBusEvent(AppWidgetProviderChangedEvent event) {
@@ -589,7 +662,7 @@
MetricsLogger.count(this, "overview_app_info", 1);
}
- public final void onBusEvent(DismissTaskEvent event) {
+ public final void onBusEvent(DismissTaskViewEvent event) {
// Remove any stored data from the loader
RecentsTaskLoader loader = Recents.getTaskLoader();
loader.deleteTaskData(event.task, false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index e2e0e918..aca816e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -28,8 +28,6 @@
*/
public class RecentsActivityLaunchState {
- public RecentsConfiguration mConfig;
-
public boolean launchedWithAltTab;
public boolean launchedWithNoRecentTasks;
public boolean launchedFromAppWithThumbnail;
@@ -41,10 +39,6 @@
public int launchedNumVisibleTasks;
public int launchedNumVisibleThumbnails;
- RecentsActivityLaunchState(RecentsConfiguration config) {
- mConfig = config;
- }
-
/** Called when the configuration has changed, and we want to reset any configuration specific
* members. */
public void updateOnConfigurationChange() {
@@ -56,7 +50,7 @@
/** Returns whether the status bar scrim should be animated when shown for the first time. */
public boolean shouldAnimateStatusBarScrim() {
- return launchedFromHome;
+ return true;
}
/** Returns whether the status bar scrim should be visible. */
@@ -72,6 +66,7 @@
/** Returns whether the nav bar scrim should be visible. */
public boolean hasNavBarScrim() {
// Only show the scrim if we have recent tasks, and if the nav bar is not transposed
- return !launchedWithNoRecentTasks && mConfig.hasTransposedNavBar;
+ RecentsConfiguration config = Recents.getConfiguration();
+ return !launchedWithNoRecentTasks && !config.hasTransposedNavBar;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index d8f4023..e2f20fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -28,7 +28,6 @@
* tied to the current activity.
*/
public class RecentsConfiguration {
- static RecentsConfiguration sInstance;
private static final int LARGE_SCREEN_MIN_DP = 600;
private static final int XLARGE_SCREEN_MIN_DP = 720;
@@ -53,7 +52,7 @@
public static final int SVELTE_DISABLE_LOADING = 3;
// Launch states
- public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(this);
+ public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
// TODO: Values determined by the current context, needs to be refactored into something that is
// agnostic of the activity context, but still calculable from the Recents component for
@@ -79,10 +78,10 @@
/** Dev options and global settings */
public boolean lockToAppEnabled;
- /** Private constructor */
- private RecentsConfiguration(Context context, SystemServicesProxy ssp) {
+ public RecentsConfiguration(Context context) {
// Load only resources that can not change after the first load either through developer
// settings or via multi window
+ SystemServicesProxy ssp = Recents.getSystemServices();
Context appContext = context.getApplicationContext();
Resources res = appContext.getResources();
useHardwareLayers = res.getBoolean(R.bool.config_recents_use_hardware_layers);
@@ -121,19 +120,6 @@
hasTransposedSearchBar = isLandscape && isLargeScreen && !isXLargeScreen;
}
- /** Updates the configuration to the current context */
- public static RecentsConfiguration initialize(Context context, SystemServicesProxy ssp) {
- if (sInstance == null) {
- sInstance = new RecentsConfiguration(context, ssp);
- }
- return sInstance;
- }
-
- /** Returns the current recents configuration */
- public static RecentsConfiguration getInstance() {
- return sInstance;
- }
-
/**
* Returns the activity launch state.
* TODO: This will be refactored out of RecentsConfiguration.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 07c7897..c314f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.recents;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
@@ -31,8 +33,10 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.MutableBoolean;
+import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.View;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -40,6 +44,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -52,7 +57,7 @@
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -134,7 +139,6 @@
boolean mCanReuseTaskStackViews = true;
// Task launching
- RecentsConfiguration mConfig;
Rect mSearchBarBounds = new Rect();
Rect mTaskStackBounds = new Rect();
Rect mLastTaskViewBounds = new Rect();
@@ -173,7 +177,6 @@
ssp.registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
- mConfig = RecentsConfiguration.initialize(mContext, ssp);
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
@@ -204,7 +207,7 @@
public void onConfigurationChanged() {
// Don't reuse task stack views if the configuration changes
mCanReuseTaskStackViews = false;
- mConfig.updateOnConfigurationChange();
+ Recents.getConfiguration().updateOnConfigurationChange();
}
/**
@@ -265,26 +268,38 @@
mTriggeredFromAltTab = false;
try {
- // If the user has toggled it too quickly, then just eat up the event here (it's better
- // than showing a janky screenshot).
- // NOTE: Ideally, the screenshot mechanism would take the window transform into account
- if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
- return;
- }
-
- // If Recents is the front most activity, then we should just communicate with it
- // directly to launch the first task or dismiss itself
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean isTopTaskHome = new MutableBoolean(true);
if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
- // Notify recents to toggle itself
- EventBus.getDefault().post(new ToggleRecentsEvent());
- mLastToggleTime = SystemClock.elapsedRealtime();
+ if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+ // Notify recents to move onto the next task
+ EventBus.getDefault().post(new IterateRecentsEvent());
+ } else {
+ // If the user has toggled it too quickly, then just eat up the event here (it's
+ // better than showing a janky screenshot).
+ // NOTE: Ideally, the screenshot mechanism would take the window transform into
+ // account
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+ return;
+ }
+
+ EventBus.getDefault().post(new ToggleRecentsEvent());
+ mLastToggleTime = SystemClock.elapsedRealtime();
+ }
return;
} else {
+ // If the user has toggled it too quickly, then just eat up the event here (it's
+ // better than showing a janky screenshot).
+ // NOTE: Ideally, the screenshot mechanism would take the window transform into
+ // account
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+ return;
+ }
+
// Otherwise, start the recents activity
startRecentsActivity(topTask, isTopTaskHome.value);
+ mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
Console.logRawError("Failed to launch RecentAppsIntent", e);
@@ -333,7 +348,7 @@
// Return early if there is no running task (can't determine affiliated tasks in this case)
if (runningTask == null) return;
// Return early if the running task is in the home stack (optimization)
- if (ssp.isInHomeStack(runningTask.id)) return;
+ if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
// Find the task in the recents list
ArrayList<Task> tasks = focusedStack.getTasks();
@@ -405,6 +420,16 @@
showRelativeAffiliatedTask(false);
}
+ public void dockTopTask() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+ if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
+ ssp.startTaskInDockedMode(topTask.id,
+ ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ showRecents(false /* triggeredFromAltTab */);
+ }
+ }
+
/**
* Returns the preloaded load plan and invalidates it.
*/
@@ -422,31 +447,32 @@
* is not already bound (can be expensive)
*/
private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
+ RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
Rect windowRect = ssp.getWindowRect();
// Update the configuration for the current state
- mConfig.update(mContext, ssp, ssp.getWindowRect());
+ config.update(mContext, ssp, ssp.getWindowRect());
if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
// Try and pre-emptively bind the search widget on startup to ensure that we
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
- mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
+ config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
}
}
Rect systemInsets = new Rect(0, mStatusBarHeight,
- (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
- (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
- mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+ (config.hasTransposedNavBar ? mNavBarWidth : 0),
+ (config.hasTransposedNavBar ? 0 : mNavBarHeight));
+ config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
mSearchBarBounds, mTaskStackBounds);
// Rebind the header bar and draw it for the transition
- TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+ TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
algo.setSystemInsets(systemInsets);
- algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
+ algo.initialize(taskStackBounds);
Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
if (!taskViewBounds.equals(mLastTaskViewBounds)) {
mLastTaskViewBounds.set(taskViewBounds);
@@ -535,12 +561,44 @@
*/
private ActivityOptions getThumbnailTransitionActivityOptions(
ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+ if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
+ stackView.getScroller().setStackScrollToInitialState();
+ ArrayList<Task> tasks = stack.getTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+ mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+ stackView.getScroller().getStackScroll(), mTmpTransform, null);
+ Rect toTaskRect = new Rect();
+ mTmpTransform.rect.round(toTaskRect);
+ Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
+ specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
+ }
+ }
+ AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
+ specs.toArray(specsArray);
+ return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+ specsArray, mHandler, this);
+ } else {
+ // Update the destination rect
+ Task toTask = new Task();
+ TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+ topTask.id, toTask);
+ RectF toTaskRect = toTransform.rect;
+ Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
+ if (thumbnail != null) {
+ return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+ thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
+ (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+ }
+ // If both the screenshot and thumbnail fails, then just fall back to the default transition
+ return getUnknownTransitionActivityOptions();
+ }
+ }
- // Update the destination rect
- Task toTask = new Task();
- TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
- topTask.id, toTask);
- RectF toTaskRect = toTransform.rect;
+ private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
+ TaskViewTransform toTransform) {
Bitmap thumbnail;
if (mThumbnailTransitionBitmapCacheKey != null
&& mThumbnailTransitionBitmapCacheKey.key != null
@@ -552,14 +610,7 @@
preloadIcon(topTask);
thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
}
- if (thumbnail != null) {
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
- (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
- }
-
- // If both the screenshot and thumbnail fails, then just fall back to the default transition
- return getUnknownTransitionActivityOptions();
+ return thumbnail;
}
/**
@@ -644,7 +695,7 @@
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack);
- TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
@@ -691,11 +742,12 @@
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
- TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+ TaskStackLayoutAlgorithm.VisibilityReport vr) {
mStartAnimationTriggered = false;
// Update the configuration based on the launch options
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
launchState.launchedFromHome = fromSearchHome || fromHome;
launchState.launchedFromSearchHome = fromSearchHome;
launchState.launchedFromAppWithThumbnail = fromThumbnail;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 31ee8ad..28299d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -234,14 +234,6 @@
dismissAllowingStateLoss();
mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation();
- // In debug mode, we force all task to be resizeable regardless of the
- // current app configuration.
- for (int i = additionalTasks; i >= 0; --i) {
- if (mTasks[i] != null) {
- ssp.setTaskResizeable(mTasks[i].key.id);
- }
- }
-
// Show tasks as they might not be currently visible - beginning with the oldest so that
// the focus ends on the selected one.
for (int i = additionalTasks; i >= 0; --i) {
@@ -277,8 +269,7 @@
if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
int taskId = mTasks[0].key.id;
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.setTaskResizeable(taskId);
- ssp.dockTask(taskId, createMode);
+ ssp.startTaskInDockedMode(taskId, createMode);
mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID);
} else {
Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 231843e..10075bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -214,11 +214,8 @@
.setVisibility(View.INVISIBLE);
}
- final int description = mAccessibilityService.isEnabled()
- ? R.string.screen_pinning_description_accessible
- : R.string.screen_pinning_description;
((TextView) mLayout.findViewById(R.id.screen_pinning_description))
- .setText(description);
+ .setText(R.string.screen_pinning_description);
final int backBgVisibility =
mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
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 fec0fc5..deae4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -200,7 +200,8 @@
*/
public class EventBus extends BroadcastReceiver {
- public static final String TAG = "EventBus";
+ private static final String TAG = "EventBus";
+ private static final boolean DEBUG_TRACE_ALL = false;
/**
* An event super class that allows us to track internal event state across subscriber
@@ -277,9 +278,6 @@
// The default priority of all subscribers
private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
- // Used for debugging everything
- private static final boolean DEBUG_TRACE_ALL = false;
-
// Orders the handlers by priority and registration time
private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
new file mode 100644
index 0000000..f7b2706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the user taps on the Overview button to iterate to the next item in the
+ * Recents list.
+ */
+public class IterateRecentsEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
index 12e5d3d..968890a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
@@ -23,12 +23,12 @@
/**
* This is sent when a {@link TaskView} has been dismissed.
*/
-public class DismissTaskEvent extends EventBus.Event {
+public class DismissTaskViewEvent extends EventBus.Event {
public final Task task;
public final TaskView taskView;
- public DismissTaskEvent(Task task, TaskView taskView) {
+ public DismissTaskViewEvent(Task task, TaskView taskView) {
this.task = task;
this.taskView = taskView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
new file mode 100644
index 0000000..9f3e9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Dismisses the currently focused task view.
+ */
+public class DismissFocusedTaskViewEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
new file mode 100644
index 0000000..171ab5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the next task view in the stack.
+ */
+public class FocusNextTaskViewEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
new file mode 100644
index 0000000..22469e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui.focus;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Focuses the previous task view in the stack.
+ */
+public class FocusPreviousTaskViewEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 735f79f..336d2db 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -19,8 +19,8 @@
import android.os.Handler;
/**
- * A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to
- * wake it up, but it will fall asleep if left untouched.
+ * A dozer is a class that fires a trigger after it falls asleep.
+ * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched.
*/
public class DozeTrigger {
@@ -28,7 +28,7 @@
boolean mIsDozing;
boolean mHasTriggered;
- int mDozeDurationSeconds;
+ int mDozeDurationMilliseconds;
Runnable mSleepRunnable;
// Sleep-runnable
@@ -41,9 +41,9 @@
}
};
- public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
+ public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) {
mHandler = new Handler();
- mDozeDurationSeconds = dozeDurationSeconds;
+ mDozeDurationMilliseconds = dozeDurationMilliseconds;
mSleepRunnable = sleepRunnable;
}
@@ -69,7 +69,7 @@
/** Poke this dozer to wake it up for a little bit. */
void forcePoke() {
mHandler.removeCallbacks(mDozeRunnable);
- mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
+ mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds);
mIsDozing = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
index 8e85bfc..ea6821f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -48,6 +48,7 @@
float[] xp;
float[] px;
+ float mLength;
CurveFunction mFn;
ParametricCurveFunction mScaleFn;
@@ -79,6 +80,7 @@
dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
pLength += dx[xStep];
}
+ mLength = pLength;
// Approximate p(x), a function of cumulative progress with x, normalized to 0..1
float p = 0;
px[0] = 0f;
@@ -260,4 +262,11 @@
}
return 1f - xToP(maxX, bounds);
}
+
+ /**
+ * Returns the length of this curve.
+ */
+ public float getArcLength() {
+ return mLength;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index a51e475..141562c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -247,8 +247,10 @@
return true;
}
+ // Note, this is only valid because we currently only allow the recents and home
+ // activities in the home stack
if (isHomeTopMost != null) {
- isHomeTopMost.value = isInHomeStack(topTask.id);
+ isHomeTopMost.value = SystemServicesProxy.isHomeStack(topTask.stackId);
}
}
return false;
@@ -266,17 +268,6 @@
return null;
}
- /** Allow a task to resize. */
- public void setTaskResizeable(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.setTaskResizeable(taskId, true);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
/**
* Resizes the given task to the new bounds.
*/
@@ -290,12 +281,14 @@
}
}
- /** Docks a task to the side of the screen. */
- public void dockTask(int taskId, int createMode) {
+ /** Docks a task to the side of the screen and starts it. */
+ public void startTaskInDockedMode(int taskId, int createMode) {
if (mIam == null) return;
try {
- mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setDockCreateMode(createMode);
+ mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, options.toBundle());
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -313,16 +306,18 @@
}
}
- /** Returns whether the specified task is in the home stack */
- public boolean isInHomeStack(int taskId) {
- if (mAm == null) return false;
+ /**
+ * Returns whether the given stack id is the home stack id.
+ */
+ public static boolean isHomeStack(int stackId) {
+ return stackId == ActivityManager.HOME_STACK_ID;
+ }
- // If we are mocking, then just return false
- if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
- return false;
- }
-
- return mAm.isInHomeStack(taskId);
+ /**
+ * Returns whether the given stack id is the freeform workspace stack id.
+ */
+ public static boolean isFreeformStack(int stackId) {
+ return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 8de8e15..6c83b87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -41,6 +41,7 @@
* options specified, such that we can transition into the Recents activity seamlessly
*/
public class RecentsTaskLoadPlan {
+
private static String TAG = "RecentsTaskLoadPlan";
private static boolean DEBUG = false;
@@ -58,39 +59,50 @@
}
Context mContext;
- RecentsConfiguration mConfig;
List<ActivityManager.RecentTaskInfo> mRawTasks;
TaskStack mStack;
/** Package level ctor */
- RecentsTaskLoadPlan(Context context, RecentsConfiguration config) {
+ RecentsTaskLoadPlan(Context context) {
mContext = context;
- mConfig = config;
}
/**
- * An optimization to preload the raw list of tasks.
+ * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
+ * to most-recent order.
*/
public synchronized void preloadRawTasks(boolean isTopTaskHome) {
SystemServicesProxy ssp = Recents.getSystemServices();
mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
+ // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
- if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
+ if (DEBUG) {
+ Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
+ for (ActivityManager.RecentTaskInfo info : mRawTasks) {
+ Log.d(TAG, " " + info.baseIntent + ", " + info.lastActiveTime);
+ }
+ }
}
/**
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
+ *
+ * The tasks will be ordered by:
+ * - least-recent to most-recent stack tasks
+ * - least-recent to most-recent freeform tasks
*/
public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
+ RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = mContext.getResources();
+ ArrayList<Task> freeformTasks = new ArrayList<>();
ArrayList<Task> stackTasks = new ArrayList<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
@@ -113,26 +125,31 @@
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
Bitmap icon = t.taskDescription != null
- ? t.taskDescription.getInMemoryIcon()
- : null;
+ ? t.taskDescription.getInMemoryIcon() : null;
String iconFilename = t.taskDescription != null
- ? t.taskDescription.getIconFilename()
- : null;
+ ? t.taskDescription.getIconFilename() : null;
// Add the task to the stack
Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
- activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
+ activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
- stackTasks.add(task);
+ if (task.isFreeformTask()) {
+ freeformTasks.add(task);
+ } else {
+ stackTasks.add(task);
+ }
}
// Initialize the stacks
+ ArrayList<Task> allTasks = new ArrayList<>();
+ allTasks.addAll(stackTasks);
+ allTasks.addAll(freeformTasks);
mStack = new TaskStack();
- mStack.setTasks(stackTasks);
+ mStack.setTasks(allTasks);
mStack.createAffiliatedGroupings(mContext);
}
@@ -145,6 +162,7 @@
", # thumbnails: " + opts.numVisibleTaskThumbnails +
", running task id: " + opts.runningTaskId);
+ RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = mContext.getResources();
@@ -175,9 +193,9 @@
if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
if (task.thumbnail == null || isRunningTask) {
if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
- if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ if (config.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true);
- } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
+ } else if (config.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
loadQueue.addTask(task);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ea97b71..71e3957 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -155,7 +155,7 @@
}
}
} else {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
+ RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
// If we've stopped the loader, then fall through to the above logic to wait on
// the load thread
@@ -320,8 +320,7 @@
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
return plan;
}
@@ -383,7 +382,7 @@
* out of memory.
*/
public void onTrimMemory(int level) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
+ RecentsConfiguration config = Recents.getConfiguration();
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
@@ -528,7 +527,7 @@
}
if (loadIfNotCached) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
+ RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
thumbnail = ssp.getTaskThumbnail(taskKey.id);
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 0793180..60051b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -170,6 +170,12 @@
}
}
+ public boolean isFreeformTask() {
+ // Temporarily disable:
+ return false;
+ // return SystemServicesProxy.isFreeformStack(key.stackId);
+ }
+
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
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 b3937c3..a96fe98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -399,6 +399,21 @@
return mTaskList.size();
}
+ /**
+ * Returns the task in this stack which is the launch target.
+ */
+ public Task getLaunchTarget() {
+ ArrayList<Task> tasks = mTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.isLaunchTarget) {
+ return task;
+ }
+ }
+ return null;
+ }
+
/** Returns the index of this task in this current task stack */
public int indexOfTask(Task t) {
return mTaskList.indexOf(t);
@@ -417,6 +432,21 @@
return null;
}
+ /**
+ * Returns whether this stack has freeform tasks.
+ */
+ public boolean hasFreeformTasks() {
+ ArrayList<Task> tasks = mTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/******** Filtering ********/
/** Filters the stack into tasks similar to the one specified */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index fb05c01..21b6bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -20,13 +20,12 @@
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
/* An outline provider that has a clip and outline that can be animated. */
public class AnimateableViewBounds extends ViewOutlineProvider {
- RecentsConfiguration mConfig;
-
TaskView mSourceView;
Rect mClipRect = new Rect();
Rect mClipBounds = new Rect();
@@ -35,7 +34,6 @@
final float mMinAlpha = 0.25f;
public AnimateableViewBounds(TaskView source, int cornerRadius) {
- mConfig = RecentsConfiguration.getInstance();
mSourceView = source;
mCornerRadius = cornerRadius;
setClipBottom(getClipBottom());
@@ -64,7 +62,9 @@
mClipRect.bottom = bottom;
mSourceView.invalidateOutline();
updateClipBounds();
- if (!mConfig.useHardwareLayers) {
+
+ RecentsConfiguration config = Recents.getConfiguration();
+ if (!config.useHardwareLayers) {
mSourceView.mThumbnailView.updateThumbnailVisibility(
bottom - mSourceView.getPaddingBottom());
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
new file mode 100644
index 0000000..ce993c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.util.Log;
+import com.android.systemui.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The layout logic for the contents of the freeform workspace.
+ */
+public class FreeformWorkspaceLayoutAlgorithm {
+
+ private static final String TAG = "FreeformWorkspaceLayoutAlgorithm";
+ private static final boolean DEBUG = false;
+
+ // The number of cells in the freeform workspace
+ private int mFreeformCellXCount;
+ private int mFreeformCellYCount;
+ // The width and height of the cells in the freeform workspace
+ private int mFreeformCellWidth;
+ private int mFreeformCellHeight;
+
+ // Optimization, allows for quick lookup of task -> index
+ private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
+
+ /**
+ * Updates the layout for each of the freeform workspace tasks. This is called after the stack
+ * layout is updated.
+ */
+ public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ int numFreeformTasks = stackLayout.mNumFreeformTasks;
+ if (!freeformTasks.isEmpty()) {
+ // Calculate the cell width/height depending on the number of freeform tasks
+ mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+ mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
+ mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+ // For now, make the cells square
+ mFreeformCellHeight = mFreeformCellWidth;
+
+ // Put each of the tasks in the progress map at a fixed index (does not need to actually
+ // map to a scroll position, just by index)
+ int taskCount = freeformTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = freeformTasks.get(i);
+ mTaskIndexMap.put(task.key, i);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "mFreeformCellXCount: " + mFreeformCellXCount);
+ Log.d(TAG, "mFreeformCellYCount: " + mFreeformCellYCount);
+ Log.d(TAG, "mFreeformCellWidth: " + mFreeformCellWidth);
+ Log.d(TAG, "mFreeformCellHeight: " + mFreeformCellHeight);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the transform is available for the given task.
+ */
+ public boolean isTransformAvailable(Task task, float stackScroll,
+ TaskStackLayoutAlgorithm stackLayout) {
+ if (stackLayout.mNumFreeformTasks == 0 || task == null ||
+ !mTaskIndexMap.containsKey(task.key)) {
+ return false;
+ }
+ return stackScroll > stackLayout.mStackEndScrollP;
+ }
+
+ /**
+ * Returns the transform for the given task. Any rect returned will be offset by the actual
+ * transform for the freeform workspace.
+ */
+ public TaskViewTransform getTransform(Task task, float stackScroll,
+ TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+ if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+ // This is a freeform task, so lay it out in the freeform workspace
+ int taskIndex = mTaskIndexMap.get(task.key);
+ int x = taskIndex % mFreeformCellXCount;
+ int y = taskIndex / mFreeformCellXCount;
+ float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
+ int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
+ int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
+ transformOut.scale = scale * 0.9f;
+ transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
+ transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+ transformOut.visible = true;
+ return transformOut;
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 7f618e3..a8a8259 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -49,7 +49,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -69,6 +69,7 @@
public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
private static final String TAG = "RecentsView";
+ private static final boolean DEBUG = false;
private static final boolean ADD_HEADER_BITMAP = true;
@@ -80,7 +81,6 @@
public void runAfterPause(Runnable r);
}
- RecentsConfiguration mConfig;
LayoutInflater mInflater;
ArrayList<TaskStack> mStacks;
@@ -116,7 +116,6 @@
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setWillNotDraw(false);
- mConfig = RecentsConfiguration.getInstance();
mInflater = LayoutInflater.from(context);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
@@ -130,7 +129,8 @@
/** Set/get the bsp root node */
public void setTaskStack(TaskStack stack) {
- if (mConfig.getLaunchState().launchedReuseTaskStackViews) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ if (config.getLaunchState().launchedReuseTaskStackViews) {
if (mTaskStackView != null) {
// If onRecentsHidden is not triggered, we need to the stack view again here
mTaskStackView.reset();
@@ -310,13 +310,14 @@
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ RecentsConfiguration config = Recents.getConfiguration();
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// Get the search bar bounds and measure the search bar layout
Rect searchBarSpaceBounds = new Rect();
if (mSearchBar != null) {
- mConfig.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+ config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
searchBarSpaceBounds);
mSearchBar.measure(
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
@@ -324,7 +325,7 @@
}
Rect taskStackBounds = new Rect();
- mConfig.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+ config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
@@ -345,11 +346,13 @@
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ RecentsConfiguration config = Recents.getConfiguration();
+
// Get the search bar bounds so that we lay it out
Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
Rect searchBarSpaceBounds = new Rect();
if (mSearchBar != null) {
- mConfig.getSearchBarBounds(measuredRect,
+ config.getSearchBarBounds(measuredRect,
mSystemInsets.top, searchBarSpaceBounds);
mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
@@ -404,22 +407,6 @@
return super.verifyDrawable(who);
}
- /** Focuses the next task in the first stack view */
- public void focusNextTask(boolean forward) {
- // Get the first stack view
- if (mTaskStackView != null) {
- mTaskStackView.focusNextTask(forward, true);
- }
- }
-
- /** Dismisses the focused task. */
- public void dismissFocusedTask() {
- // Get the first stack view
- if (mTaskStackView != null) {
- mTaskStackView.dismissFocusedTask();
- }
- }
-
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mStacks != null) {
@@ -562,7 +549,7 @@
// Disable any focused state before we draw the header
// Upfront the processing of the thumbnail
if (tv.isFocusedTask()) {
- tv.unsetFocusedTask();
+ tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
}
TaskViewTransform transform = new TaskViewTransform();
transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
@@ -682,7 +669,7 @@
} else {
// Dismiss the task and return the user to home if we fail to
// launch the task
- EventBus.getDefault().send(new DismissTaskEvent(task, tv));
+ EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
if (mCb != null) {
mCb.onTaskLaunchFailed();
}
@@ -800,8 +787,7 @@
// Dock the new task if we are hovering over a valid dock state
if (event.dockState != TaskStack.DockState.NONE) {
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.setTaskResizeable(event.task.key.id);
- ssp.dockTask(event.task.key.id, event.dockState.createMode);
+ ssp.startTaskInDockedMode(event.task.key.id, event.dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index cf4c9cb..f3a4390 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -19,6 +19,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.view.MotionEvent;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
@@ -34,14 +35,16 @@
*/
class DockRegion {
public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+ // We only allow docking to the left for now on small devices
TaskStack.DockState.LEFT
};
public static TaskStack.DockState[] PHONE_PORTRAIT = {
- // We only allow docking to the top for now
+ // We only allow docking to the top for now on small devices
TaskStack.DockState.TOP
};
public static TaskStack.DockState[] TABLET_LANDSCAPE = {
- TaskStack.DockState.LEFT
+ TaskStack.DockState.LEFT,
+ TaskStack.DockState.RIGHT
};
public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
}
@@ -71,7 +74,7 @@
public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
- RecentsConfiguration config = RecentsConfiguration.getInstance();
+ RecentsConfiguration config = Recents.getConfiguration();
TaskStack.DockState[] dockStates = isLandscape ?
(config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) :
(config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index e04699c1..b7c1de3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -68,6 +68,7 @@
private float mInitialTouchPos;
private boolean mDragging;
+ private float mSnapBackTranslationX;
private View mCurrView;
private boolean mCanCurrViewBeDimissed;
@@ -92,6 +93,10 @@
mDensityScale = densityScale;
}
+ public void setSnapBackTranslationX(float translationX) {
+ mSnapBackTranslationX = translationX;
+ }
+
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
}
@@ -267,7 +272,7 @@
private void snapChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
- ValueAnimator anim = createTranslationAnimation(view, 0);
+ ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.setInterpolator(mLinearOutSlowInInterpolator);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 5fbea00..c4e2d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -22,6 +22,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
@@ -31,7 +32,6 @@
public class SystemBarScrimViews {
Context mContext;
- RecentsConfiguration mConfig;
View mStatusBarScrimView;
View mNavBarScrimView;
@@ -48,7 +48,6 @@
public SystemBarScrimViews(Activity activity) {
mContext = activity;
- mConfig = RecentsConfiguration.getInstance();
mStatusBarScrimView = activity.findViewById(R.id.status_bar_scrim);
mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
mNavBarScrimEnterDuration = activity.getResources().getInteger(
@@ -64,7 +63,8 @@
* the first draw.
*/
public void prepareEnterRecentsAnimation() {
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
mHasNavBarScrim = launchState.hasNavBarScrim();
mShouldAnimateNavBarScrim = launchState.shouldAnimateNavBarScrim();
mHasStatusBarScrim = launchState.hasStatusBarScrim();
@@ -82,7 +82,8 @@
* Starts animating the scrim views when entering Recents.
*/
public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
int transitionEnterFromAppDelay = mContext.getResources().getInteger(
R.integer.recents_enter_from_app_transition_duration);
int transitionEnterFromHomeDelay = mContext.getResources().getInteger(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
new file mode 100644
index 0000000..ff02e03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.Log;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+/**
+ * The layout logic for a TaskStackView.
+ */
+public class TaskStackLayoutAlgorithm {
+
+ private static final String TAG = "TaskStackViewLayoutAlgorithm";
+ private static final boolean DEBUG = false;
+
+ // The min scale of the last task at the top of the curve
+ private static final float STACK_PEEK_MIN_SCALE = 0.85f;
+ // The scale of the last task
+ private static final float SINGLE_TASK_SCALE = 0.95f;
+ // The percentage of height of task to show between tasks
+ private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+ // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
+ // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
+ // workspace)
+ private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
+
+ // A report of the visibility state of the stack
+ public class VisibilityReport {
+ public int numVisibleTasks;
+ public int numVisibleThumbnails;
+
+ /** Package level ctor */
+ VisibilityReport(int tasks, int thumbnails) {
+ numVisibleTasks = tasks;
+ numVisibleThumbnails = thumbnails;
+ }
+ }
+
+ Context mContext;
+
+ // This is the view bounds inset exactly by the search bar, but without the bottom inset
+ // see RecentsConfiguration.getTaskStackBounds()
+ public Rect mStackRect = new Rect();
+ // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
+ // of the stack rect
+ public Rect mTaskRect = new Rect();
+ // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
+ public Rect mFreeformRect = new Rect();
+ // This is the current system insets
+ public Rect mSystemInsets = new Rect();
+
+ // The smallest scroll progress, at this value, the back most task will be visible
+ float mMinScrollP;
+ // The largest scroll progress, at this value, the front most task will be visible above the
+ // navigation bar
+ float mMaxScrollP;
+ // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
+ // of the stack
+ float mStackEndScrollP;
+ // The scroll progress that we actually want to scroll the user to when they want to go to the
+ // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
+ // the bottom of the stack)
+ float mPreferredStackEndScrollP;
+ // The initial progress that the scroller is set when you first enter recents
+ float mInitialScrollP;
+ // The task progress for the front-most task in the stack
+ float mFrontMostTaskP;
+
+ // The relative progress to ensure that the height between affiliated tasks is respected
+ float mWithinAffiliationPOffset;
+ // The relative progress to ensure that the height between non-affiliated tasks is
+ // respected
+ float mBetweenAffiliationPOffset;
+ // The relative progress to ensure that the task height is respected
+ float mTaskHeightPOffset;
+ // The relative progress to ensure that the half task height is respected
+ float mTaskHalfHeightPOffset;
+ // The front-most task bottom offset
+ int mStackBottomOffset;
+ // The relative progress to ensure that the offset from the bottom of the stack to the bottom
+ // of the task is respected
+ float mStackBottomPOffset;
+ // The freeform workspace gap
+ int mFreeformWorkspaceGapOffset;
+ float mFreeformWorkspaceGapPOffset;
+ // The relative progress to ensure that the freeform workspace height + gap + stack bottom
+ // padding is respected
+ int mFreeformWorkspaceOffset;
+ float mFreeformWorkspacePOffset;
+
+ // The last computed task counts
+ int mNumStackTasks;
+ int mNumFreeformTasks;
+ // The min/max z translations
+ int mMinTranslationZ;
+ int mMaxTranslationZ;
+
+ // Optimization, allows for quick lookup of task -> progress
+ HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
+
+ // The freeform workspace layout
+ FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+
+ // Temporary task view transform
+ TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+ // Log function
+ static ParametricCurve sCurve;
+
+ public TaskStackLayoutAlgorithm(Context context) {
+ Resources res = context.getResources();
+ mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
+ mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
+ mContext = context;
+ mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
+ if (sCurve == null) {
+ sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
+ // The large the XScale, the longer the flat area of the curve
+ private static final float XScale = 1.75f;
+ private static final float LogBase = 3000;
+
+ float reverse(float x) {
+ return (-x * XScale) + 1;
+ }
+
+ @Override
+ public float f(float x) {
+ return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
+ }
+
+ @Override
+ public float invF(float y) {
+ return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
+ }
+ }, new ParametricCurve.ParametricCurveFunction() {
+ @Override
+ public float f(float p) {
+ // Don't scale when there are freeform tasks
+ if (mNumFreeformTasks > 0) {
+ return 1f;
+ }
+
+ if (p < 0) return STACK_PEEK_MIN_SCALE;
+ if (p > 1) return 1f;
+ float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
+ float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
+ return scale;
+ }
+ });
+ }
+ }
+
+ /**
+ * Sets the system insets.
+ */
+ public void setSystemInsets(Rect systemInsets) {
+ mSystemInsets.set(systemInsets);
+ if (DEBUG) {
+ Log.d(TAG, "setSystemInsets: " + systemInsets);
+ }
+ }
+
+ /**
+ * Computes the stack and task rects.
+ */
+ public void initialize(Rect taskStackBounds) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
+ int heightPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_stack_top_padding);
+
+ // Compute the stack rect, inset from the given task stack bounds
+ mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
+ taskStackBounds.right - widthPadding, taskStackBounds.bottom);
+ mStackBottomOffset = mSystemInsets.bottom + heightPadding;
+
+ // Compute the task rect, align it to the top-center square in the stack rect
+ int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
+ int xOffset = (mStackRect.width() - size) / 2;
+ mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
+ mStackRect.right - xOffset, mStackRect.top + size);
+
+ // Compute the freeform rect, align it to the top-left of the stack rect
+ mFreeformRect.set(mStackRect);
+ mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
+
+ // Compute the progress offsets
+ int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_bar_height);
+ int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
+ mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
+ mStackRect);
+ mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
+ mStackRect);
+ mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
+ mStackRect);
+ mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
+ mStackRect);
+ mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
+ mFreeformWorkspaceGapOffset = mStackBottomOffset;
+ mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
+ mStackRect);
+ mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
+ mStackBottomOffset;
+ mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
+ mStackRect);
+
+ if (DEBUG) {
+ Log.d(TAG, "initialize");
+ Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
+ Log.d(TAG, "\tmStackRect: " + mStackRect);
+ Log.d(TAG, "\tmTaskRect: " + mTaskRect);
+ Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
+
+ Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
+ Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
+ Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
+ Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
+ Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
+ Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
+ Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
+
+ Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
+ Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
+
+ for (int height = 0; height <= 2000; height += 50) {
+ float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
+ float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
+ Log.d(TAG, "offset: " + height + ", " +
+ p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
+ sCurve.pToScale(1f - p) + ", " +
+ p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
+ }
+ }
+ }
+
+ /**
+ * Computes the minimum and maximum scroll progress values and the progress values for each task
+ * in the stack.
+ */
+ void update(TaskStack stack) {
+ if (DEBUG) {
+ Log.d(TAG, "update");
+ }
+
+ // Clear the progress map
+ mTaskProgressMap.clear();
+
+ // Return early if we have no tasks
+ ArrayList<Task> tasks = stack.getTasks();
+ if (tasks.isEmpty()) {
+ mFrontMostTaskP = 0;
+ mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
+ mNumStackTasks = mNumFreeformTasks = 0;
+ return;
+ }
+
+ // Filter the set of freeform and stack tasks
+ ArrayList<Task> freeformTasks = new ArrayList<>();
+ ArrayList<Task> stackTasks = new ArrayList<>();
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ freeformTasks.add(task);
+ } else {
+ stackTasks.add(task);
+ }
+ }
+ mNumStackTasks = stackTasks.size();
+ mNumFreeformTasks = freeformTasks.size();
+
+ if (!stackTasks.isEmpty()) {
+ // Update the for each task from back to front.
+ float pAtBackMostTaskTop = 0;
+ float pAtFrontMostTaskTop = pAtBackMostTaskTop;
+ int taskCount = stackTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = stackTasks.get(i);
+ mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+
+ if (i < (taskCount - 1)) {
+ // Increment the peek height
+ float pPeek = task.group.isFrontMostTask(task) ?
+ mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
+ pAtFrontMostTaskTop += pPeek;
+ }
+ }
+
+ mFrontMostTaskP = pAtFrontMostTaskTop;
+ // Set the stack end scroll progress to the point at which the bottom of the front-most
+ // task is aligned to the bottom of the stack
+ mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+ mTaskHeightPOffset);
+ // Set the preferred stack end scroll progress to the point where the bottom of the
+ // front-most task is offset by the navbar and padding from the bottom of the stack
+ mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
+ // Basically align the back-most task such that its progress is the same as the top of
+ // the front most task at the max stack scroll
+ mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
+ } else {
+ // TODO: In the case where there is only freeform tasks, then the scrolls should be
+ // set to zero
+ }
+
+ if (!freeformTasks.isEmpty()) {
+ // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
+ // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
+ // workspace will be visible
+ mFreeformLayoutAlgorithm.update(freeformTasks, this);
+ mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
+ mInitialScrollP = isInitialStateFreeform(stack) ?
+ mMaxScrollP : mPreferredStackEndScrollP;
+ } else {
+ mMaxScrollP = mPreferredStackEndScrollP;
+ mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
+ Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
+ Log.d(TAG, "mMinScrollP: " + mMinScrollP);
+ Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
+ Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
+ }
+ }
+
+ /**
+ * Computes the maximum number of visible tasks and thumbnails. Requires that
+ * update() is called first.
+ */
+ public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
+ // Ensure minimum visibility count
+ if (tasks.size() <= 1) {
+ return new VisibilityReport(1, 1);
+ }
+
+ // If there are freeform tasks, then they will be the only ones visible
+ int freeformTaskCount = 0;
+ for (Task t : tasks) {
+ if (t.isFreeformTask()) {
+ freeformTaskCount++;
+ }
+ }
+ if (freeformTaskCount > 0) {
+ return new VisibilityReport(freeformTaskCount, freeformTaskCount);
+ }
+
+ // Otherwise, walk backwards in the stack and count the number of tasks and visible
+ // thumbnails
+ int taskHeight = mTaskRect.height();
+ int taskBarHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_bar_height);
+ int numVisibleTasks = 1;
+ int numVisibleThumbnails = 1;
+ float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
+ int prevScreenY = sCurve.pToX(progress, mStackRect);
+ for (int i = tasks.size() - 2; i >= 0; i--) {
+ Task task = tasks.get(i);
+ progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
+ if (progress < 0) {
+ break;
+ }
+ boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
+ if (isFrontMostTaskInGroup) {
+ float scaleAtP = sCurve.pToScale(progress);
+ int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
+ int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
+ boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
+ if (hasVisibleThumbnail) {
+ numVisibleThumbnails++;
+ numVisibleTasks++;
+ prevScreenY = screenY;
+ } else {
+ // Once we hit the next front most task that does not have a visible thumbnail,
+ // w alk through remaining visible set
+ for (int j = i; j >= 0; j--) {
+ numVisibleTasks++;
+ progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
+ if (progress < 0) {
+ break;
+ }
+ }
+ break;
+ }
+ } else if (!isFrontMostTaskInGroup) {
+ // Affiliated task, no thumbnail
+ numVisibleTasks++;
+ }
+ }
+ return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
+ }
+
+ /**
+ * Returns the transform for the given task. This transform is relative to the mTaskRect, which
+ * is what the view is measured and laid out with.
+ */
+ public TaskViewTransform getStackTransform(Task task, float stackScroll,
+ TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
+ mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
+ if (transformOut.visible) {
+ getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+ transformOut.translationY += mTmpTransform.translationY;
+ transformOut.translationZ = mMaxTranslationZ;
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.p = 0;
+ }
+ return transformOut;
+ } else {
+ // Return early if we have an invalid index
+ if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+ transformOut.reset();
+ return transformOut;
+ }
+ return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
+ prevTransform);
+ }
+ }
+
+ /** Update/get the transform */
+ public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
+ TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+
+ if (mNumStackTasks == 1) {
+ // Center the task in the stack, changing the scale will not follow the curve, but just
+ // modulate some values directly
+ float pTaskRelative = mMinScrollP - stackScroll;
+ float scale = SINGLE_TASK_SCALE;
+ int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
+ transformOut.scale = scale;
+ transformOut.translationX = 0;
+ transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+ transformOut.translationZ = mMaxTranslationZ;
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = true;
+ transformOut.p = pTaskRelative;
+ return transformOut;
+
+ } else {
+ // Once we scroll past the preferred stack end scroll, then we should start translating
+ // the cards in screen space and lock their final state at the end stack progress
+ int overscrollYOffset = 0;
+ if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
+ float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
+ (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
+ overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
+ (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
+ stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
+ }
+
+ float pTaskRelative = taskProgress - stackScroll;
+ float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+
+ // If the task top is outside of the bounds below the screen, then immediately reset it
+ if (pTaskRelative > 1f) {
+ transformOut.reset();
+ transformOut.rect.set(mTaskRect);
+ return transformOut;
+ }
+ // The check for the top is trickier, since we want to show the next task if it is at
+ // all visible, even if p < 0.
+ if (pTaskRelative < 0f) {
+ if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
+ transformOut.reset();
+ transformOut.rect.set(mTaskRect);
+ return transformOut;
+ }
+ }
+ float scale = sCurve.pToScale(pBounded);
+ int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
+ transformOut.scale = scale;
+ transformOut.translationX = 0;
+ transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
+ scaleYOffset - overscrollYOffset;
+ transformOut.translationZ = Math.max(mMinTranslationZ,
+ mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = true;
+ transformOut.p = pTaskRelative;
+ if (DEBUG) {
+ Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+ Log.d(TAG, "\t" + transformOut);
+ }
+
+ return transformOut;
+ }
+ }
+
+ /**
+ * Returns whether this stack should be initialized to show the freeform workspace or not.
+ */
+ public boolean isInitialStateFreeform(TaskStack stack) {
+ Task launchTarget = stack.getLaunchTarget();
+ if (launchTarget != null) {
+ return launchTarget.isFreeformTask();
+ }
+ Task frontTask = stack.getFrontMostTask();
+ if (frontTask != null) {
+ return frontTask.isFreeformTask();
+ }
+ return false;
+ }
+
+ /**
+ * Update/get the transform
+ */
+ public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
+ TaskViewTransform transformOut) {
+ transformOut.reset();
+ if (mNumFreeformTasks == 0) {
+ return transformOut;
+ }
+
+ if (stackScroll > mStackEndScrollP) {
+ // mStackEndScroll is the point at which the first stack task is bottom aligned with the
+ // stack, so we offset from on the stack rect height.
+ float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
+ mFreeformWorkspacePOffset;
+ int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
+ transformOut.scale = 1f;
+ transformOut.alpha = 1f;
+ transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
+ overscrollYOffset;
+ transformOut.rect.set(mFreeformRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = true;
+ }
+ return transformOut;
+ }
+
+ /**
+ * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
+ */
+ public float getPreferredMaxScrollPosition(float scroll) {
+ float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
+ (mMaxScrollP - mStackEndScrollP);
+ if (scroll < maxStackScrollBounds) {
+ return mPreferredStackEndScrollP;
+ }
+ return mMaxScrollP;
+ }
+
+ /**
+ * Returns the untransformed task view bounds.
+ */
+ public Rect getUntransformedTaskViewBounds() {
+ return new Rect(mTaskRect);
+ }
+
+ /**
+ * Returns the scroll progress to scroll to such that the top of the task is at the top of the
+ * stack.
+ */
+ float getStackScrollForTask(Task t) {
+ if (!mTaskProgressMap.containsKey(t.key)) return 0f;
+ return mTaskProgressMap.get(t.key);
+ }
+
+ /**
+ * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
+ * length of the curve. We know the curve is mostly flat, so we just map the length of the
+ * screen along the arc-length proportionally (1/arclength).
+ */
+ public float getDeltaPForY(int downY, int y) {
+ float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
+ return -deltaP;
+ }
+
+ /**
+ * This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length
+ * of the curve, map back to the screen y.
+ */
+ public int getYForDeltaP(float downScrollP, float p) {
+ int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
+ return -y;
+ }
+
+ private float alignToStackTop(float p) {
+ // At scroll progress == p, then p is at the top of the stack
+ return p;
+ }
+
+ private float alignToStackBottom(float p, float pOffsetFromBottom) {
+ // At scroll progress == p, then p is at the top of the stack
+ // At scroll progress == p + 1, then p is at the bottom of the stack
+ return p - (1 - pOffsetFromBottom);
+ }
+}
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 5928854..a7bfc40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -22,7 +22,9 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -30,6 +32,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.systemui.R;
+import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -37,8 +40,11 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
+import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
@@ -60,6 +66,9 @@
TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
ViewPool.ViewPoolConsumer<TaskView, Task> {
+ private final static String TAG = "TaskStackView";
+ private final static boolean DEBUG = false;
+
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
@@ -68,26 +77,24 @@
public void onTaskStackFilterTriggered();
public void onTaskStackUnfilterTriggered();
}
- RecentsConfiguration mConfig;
TaskStack mStack;
- TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+ TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewScroller mStackScroller;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
+ ColorDrawable mFreeformWorkspaceBackground;
ViewPool<TaskView, Task> mViewPool;
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
DozeTrigger mUIDozeTrigger;
int mFocusedTaskIndex = -1;
- int mPrevAccessibilityFocusedIndex = -1;
// Optimizations
int mStackViewsAnimationDuration;
boolean mStackViewsDirty = true;
boolean mStackViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
boolean mStartEnterAnimationRequestedAfterLayout;
- boolean mStartEnterAnimationCompleted;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
Rect mTaskStackBounds = new Rect();
@@ -95,6 +102,8 @@
Rect mTmpRect = new Rect();
RectF mTmpTaskRect = new RectF();
TaskViewTransform mTmpTransform = new TaskViewTransform();
+ TaskViewTransform mTmpStackBackTransform = new TaskViewTransform();
+ TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
ArrayList<TaskView> mTaskViews = new ArrayList<>();
List<TaskView> mImmutableTaskViews = new ArrayList<>();
@@ -114,10 +123,9 @@
super(context);
// Set the stack first
setStack(stack);
- mConfig = RecentsConfiguration.getInstance();
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context, mConfig);
+ mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
@@ -138,6 +146,8 @@
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+ mFreeformWorkspaceBackground = new ColorDrawable(0x33000000);
}
/** Sets the callbacks */
@@ -219,7 +229,6 @@
mStackViewsDirty = true;
mStackViewsClipDirty = true;
mAwaitingFirstLayout = true;
- mPrevAccessibilityFocusedIndex = -1;
if (mUIDozeTrigger != null) {
mUIDozeTrigger.stopDozing();
mUIDozeTrigger.resetTrigger();
@@ -266,7 +275,7 @@
}
/** Returns the stack algorithm for this task stack. */
- public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
+ public TaskStackLayoutAlgorithm getStackAlgorithm() {
return mLayoutAlgorithm;
}
@@ -332,20 +341,24 @@
/** Synchronizes the views with the model */
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
float stackScroll = mStackScroller.getStackScroll();
int[] visibleRange = mTmpVisibleRange;
boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
stackScroll, visibleRange, false);
+ boolean hasStackBackTransform = false;
+ boolean hasStackFrontTransform = false;
+ if (DEBUG) {
+ Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+ }
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
List<TaskView> taskViews = getTaskViews();
+ boolean wasLastFocusedTaskAnimated = false;
+ int lastFocusedTaskIndex = -1;
int taskViewCount = taskViews.size();
- boolean reaquireAccessibilityFocus = false;
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
@@ -353,8 +366,12 @@
if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
+ if (tv.isFocusedTask()) {
+ wasLastFocusedTaskAnimated = tv.isFocusAnimated();
+ lastFocusedTaskIndex = taskIndex;
+ resetFocusedTask();
+ }
mViewPool.returnViewToPool(tv);
- reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex);
}
}
@@ -374,35 +391,47 @@
// For items in the list, put them in start animating them from the
// approriate ends of the list where they are expected to appear
if (Float.compare(transform.p, 0f) <= 0) {
- mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
+ if (!hasStackBackTransform) {
+ hasStackBackTransform = true;
+ mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpStackBackTransform,
+ null);
+ }
+ tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0);
} else {
- mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
+ if (!hasStackFrontTransform) {
+ hasStackFrontTransform = true;
+ mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpStackFrontTransform,
+ null);
+ }
+ tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0);
}
- tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
}
// Animate the task into place
tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+ }
- // Request accessibility focus on the next view if we removed the task
- // that previously held accessibility focus
- if (reaquireAccessibilityFocus) {
- taskViews = getTaskViews();
- taskViewCount = taskViews.size();
- if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() &&
- mPrevAccessibilityFocusedIndex != -1) {
- TaskView atv = taskViews.get(taskViewCount - 1);
- int indexOfTask = mStack.indexOfTask(atv.getTask());
- if (mPrevAccessibilityFocusedIndex != indexOfTask) {
- tv.requestAccessibilityFocus();
- mPrevAccessibilityFocusedIndex = indexOfTask;
- }
- }
+ // Update the focus if the previous focused task was returned to the view pool
+ if (lastFocusedTaskIndex != -1) {
+ if (lastFocusedTaskIndex < visibleRange[1]) {
+ setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+ } else {
+ setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
}
}
+ // Update the freeform workspace
+ mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+ if (mTmpTransform.visible) {
+ mTmpTransform.rect.roundOut(mTmpRect);
+ mFreeformWorkspaceBackground.setAlpha(255);
+ mFreeformWorkspaceBackground.setBounds(mTmpRect);
+ } else {
+ mFreeformWorkspaceBackground.setAlpha(0);
+ }
+
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
@@ -460,7 +489,7 @@
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
// Compute the min and max scroll values
- mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
+ mLayoutAlgorithm.update(mStack);
// Debug logging
if (boundScrollToNewMinMax) {
@@ -473,118 +502,80 @@
return mStackScroller;
}
- /** Focuses the task at the specified index in the stack */
- void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
- // Return early if the task is already focused
- if (taskIndex == mFocusedTaskIndex) return;
+ /**
+ * Sets the focused task to the provided (bounded taskIndex).
+ */
+ private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+ setFocusedTask(taskIndex, scrollToTask, animated, true);
+ }
- if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
- mFocusedTaskIndex = taskIndex;
- mPrevAccessibilityFocusedIndex = taskIndex;
+ /**
+ * Sets the focused task to the provided (bounded taskIndex).
+ */
+ private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+ final boolean requestViewFocus) {
+ // Find the next task to focus
+ int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
+ Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+ final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
+ mStack.getTasks().get(newFocusedTaskIndex) : null;
- // Focus the view if possible, otherwise, focus the view after we scroll into position
- final Task t = mStack.getTasks().get(mFocusedTaskIndex);
- Runnable postScrollRunnable = new Runnable() {
+ // Reset the last focused task state if changed
+ if (mFocusedTaskIndex != -1) {
+ Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex);
+ if (focusedTask != newFocusedTask) {
+ resetFocusedTask();
+ }
+ }
+
+ mFocusedTaskIndex = newFocusedTaskIndex;
+ if (mFocusedTaskIndex != -1) {
+ Runnable focusTaskRunnable = new Runnable() {
@Override
public void run() {
- TaskView tv = getChildViewForTask(t);
+ TaskView tv = getChildViewForTask(newFocusedTask);
if (tv != null) {
- tv.setFocusedTask(animateFocusedState);
- tv.requestAccessibilityFocus();
+ tv.setFocusedState(true, animated, requestViewFocus);
}
}
};
- // Scroll the view into position (just center it in the curve)
- if (scrollToNewPosition) {
- float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f;
+ if (scrollToTask) {
+ // TODO: Center the newly focused task view, only if not freeform
+ float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
- mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);
+ mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+ focusTaskRunnable);
} else {
- if (postScrollRunnable != null) {
- postScrollRunnable.run();
- }
+ focusTaskRunnable.run();
}
-
}
}
/**
- * Ensures that there is a task focused, if nothing is focused, then we will use the task
- * at the center of the visible stack.
+ * Sets the focused task relative to the currently focused task.
+ *
+ * @param animated determines whether to actually draw the highlight along with the change in
+ * focus.
*/
- public boolean ensureFocusedTask(boolean findClosestToCenter) {
- if (mFocusedTaskIndex < 0) {
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- if (findClosestToCenter) {
- // If there is no task focused, then find the task that is closes to the center
- // of the screen and use that as the currently focused task
- int x = mLayoutAlgorithm.mStackRect.centerX();
- int y = mLayoutAlgorithm.mStackRect.centerY();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- tv.getHitRect(mTmpRect);
- if (mTmpRect.contains(x, y)) {
- mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
- mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
- break;
- }
- }
- }
- // If we can't find the center task, then use the front most index
- if (mFocusedTaskIndex < 0 && taskViewCount > 0) {
- TaskView tv = taskViews.get(taskViewCount - 1);
- mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
- mPrevAccessibilityFocusedIndex = mFocusedTaskIndex;
- }
- }
- return mFocusedTaskIndex >= 0;
- }
-
- /**
- * Focuses the next task in the stack.
- * @param animateFocusedState determines whether to actually draw the highlight along with
- * the change in focus, as well as whether to scroll to fit the
- * task into view.
- */
- public void focusNextTask(boolean forward, boolean animateFocusedState) {
+ public void setRelativeFocusedTask(boolean forward, boolean animated) {
// Find the next index to focus
- int numTasks = mStack.getTaskCount();
- if (numTasks == 0) return;
-
- int direction = (forward ? -1 : 1);
- int newIndex = mFocusedTaskIndex + direction;
- if (newIndex >= 0 && newIndex <= (numTasks - 1)) {
- newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));
- focusTask(newIndex, true, animateFocusedState);
- }
+ int newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+ setFocusedTask(newIndex, true, animated);
}
- /** Dismisses the focused task. */
- public void dismissFocusedTask() {
- // Return early if the focused task index is invalid
- if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) {
- mFocusedTaskIndex = -1;
- return;
- }
-
- Task t = mStack.getTasks().get(mFocusedTaskIndex);
- TaskView tv = getChildViewForTask(t);
- tv.dismissTask();
- }
-
- /** Resets the focused task. */
+ /**
+ * Resets the focused task.
+ */
void resetFocusedTask() {
- if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) {
+ if (mFocusedTaskIndex != -1) {
Task t = mStack.getTasks().get(mFocusedTaskIndex);
TaskView tv = getChildViewForTask(t);
if (tv != null) {
- tv.unsetFocusedTask();
+ tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
}
}
mFocusedTaskIndex = -1;
- mPrevAccessibilityFocusedIndex = -1;
}
@Override
@@ -609,12 +600,12 @@
super.onInitializeAccessibilityNodeInfo(info);
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
- if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) {
+ if (taskViewCount > 1 && mFocusedTaskIndex != -1) {
info.setScrollable(true);
- if (mPrevAccessibilityFocusedIndex > 0) {
+ if (mFocusedTaskIndex > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
- if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
+ if (mFocusedTaskIndex < mStack.getTaskCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
}
@@ -630,22 +621,14 @@
if (super.performAccessibilityAction(action, arguments)) {
return true;
}
- if (ensureFocusedTask(false)) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
- if (mPrevAccessibilityFocusedIndex > 0) {
- focusNextTask(true, false);
- return true;
- }
- }
- break;
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
- if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
- focusNextTask(false, false);
- return true;
- }
- }
- break;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ setRelativeFocusedTask(true, false /* animated */);
+ return true;
+ }
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ setRelativeFocusedTask(false, false /* animated */);
+ return true;
}
}
return false;
@@ -677,10 +660,9 @@
}
/** Computes the stack and task rects */
- public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
- boolean launchedWithAltTab, boolean launchedFromHome) {
+ public void computeRects(Rect taskStackBounds) {
// Compute the rects in the stack algorithm
- mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
+ mLayoutAlgorithm.initialize(taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
@@ -699,7 +681,7 @@
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
*/
- public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
+ public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
@@ -718,9 +700,7 @@
int height = MeasureSpec.getSize(heightMeasureSpec);
// Compute our stack/task rects
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- computeRects(width, height, mTaskStackBounds, launchState.launchedWithAltTab,
- launchState.launchedFromHome);
+ computeRects(mTaskStackBounds);
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately to load all the views
@@ -741,12 +721,12 @@
mTmpRect.setEmpty();
}
tv.measure(
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
- MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
- MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+ MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -759,7 +739,7 @@
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // Layout each of the children
+ // Layout each of the TaskViews
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -769,10 +749,9 @@
} else {
mTmpRect.setEmpty();
}
- tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
- mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
- mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
- mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
+ Rect taskRect = mLayoutAlgorithm.mTaskRect;
+ tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+ taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
}
if (mAwaitingFirstLayout) {
@@ -786,17 +765,9 @@
int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
// Find the launch target task
- Task launchTargetTask = null;
+ Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isLaunchTarget) {
- launchTargetTask = task;
- break;
- }
- }
// Prepare the first view for its enter animation
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -804,7 +775,8 @@
Task task = tv.getTask();
boolean occludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
+ tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+ offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
@@ -815,17 +787,17 @@
mStartEnterAnimationContext = null;
}
- // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the
- // enter animation).
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- if (launchState.launchedWithAltTab) {
- if (launchState.launchedFromAppWithThumbnail) {
- focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
- launchState.launchedHasConfigurationChanged);
- } else {
- focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
- launchState.launchedHasConfigurationChanged);
- }
+ // Set the task focused state without requesting view focus, and leave the focus animations
+ // until after the enter-animation
+ if (!Constants.DebugFlags.App.EnableFastToggleRecents && launchTargetTask != null) {
+ setFocusedTask(mStack.indexOfTask(launchTargetTask), false /* scrollToTask */,
+ false /* animated */, false /* requestViewFocus */);
+ } else {
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ int taskOffset = launchState.launchedFromHome ? -1 : -2;
+ setFocusedTask(mStack.getTaskCount() + taskOffset, false /* scrollToTask */,
+ false /* animated */, false /* requestViewFocus */);
}
// Start dozing
@@ -843,17 +815,9 @@
if (mStack.getTaskCount() > 0) {
// Find the launch target task
- Task launchTargetTask = null;
+ Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isLaunchTarget) {
- launchTargetTask = task;
- break;
- }
- }
// Animate all the task views into view
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -866,7 +830,8 @@
ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
ctx.updateListener = mRequestUpdateClippingListener;
- mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
+ mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+ ctx.currentTaskTransform, null);
tv.startEnterRecentsAnimation(ctx);
}
@@ -874,32 +839,18 @@
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
- mStartEnterAnimationCompleted = true;
// Poke the dozer to restart the trigger after the animation completes
mUIDozeTrigger.poke();
- SystemServicesProxy ssp = Recents.getSystemServices();
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- if (taskViewCount > 0) {
- // Focus the first view if accessibility is enabled
- if (ssp.isTouchExplorationEnabled()) {
- TaskView tv = taskViews.get(taskViewCount - 1);
- tv.requestAccessibilityFocus();
- mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask());
- }
- }
-
- // Start the focus animation when alt-tabbing
- ArrayList<Task> tasks = mStack.getTasks();
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- if (launchState.launchedWithAltTab &&
- !launchState.launchedHasConfigurationChanged &&
- 0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
- TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
- if (tv != null) {
- tv.setFocusedTask(true);
- }
+ // Update the focused state here -- since we only set the focused task without
+ // requesting view focus in onFirstLayout(), actually request view focus and
+ // animate the focused state if we are alt-tabbing now, after the window enter
+ // animation is completed
+ if (mFocusedTaskIndex != -1) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ setFocusedTask(mFocusedTaskIndex, false /* scrollToTask */,
+ launchState.launchedWithAltTab);
}
}
});
@@ -961,6 +912,12 @@
@Override
protected void dispatchDraw(Canvas canvas) {
mLayersDisabled = false;
+
+ // Draw the freeform workspace background
+ if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+ mFreeformWorkspaceBackground.draw(canvas);
+ }
+
super.dispatchDraw(canvas);
}
@@ -982,42 +939,44 @@
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
- // Remove the view associated with this task, we can't rely on updateTransforms
- // to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
+ if (!removedTask.isFreeformTask()) {
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
+
+ // Get the stack scroll of the task to anchor to (since we are removing something, the front
+ // most task will be our anchor task)
+ Task anchorTask = null;
+ float prevAnchorTaskScroll = 0;
+ boolean pullStackForward = stack.getTaskCount() > 0;
+ if (pullStackForward) {
+ anchorTask = mStack.getFrontMostTask();
+ prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+ }
+
+ // Update the min/max scroll and animate other task views into their new positions
+ updateMinMaxScroll(true);
+
+ if (wasFrontMostTask) {
+ // Since the max scroll progress is offset from the bottom of the stack, just scroll
+ // to ensure that the new front most task is now fully visible
+ mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
+ } else if (pullStackForward) {
+ // Otherwise, offset the scroll by half the movement of the anchor task to allow the
+ // tasks behind the removed task to move forward, and the tasks in front to move back
+ float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+ mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
+ - prevAnchorTaskScroll) / 2);
+ mStackScroller.boundScroll();
+ }
+
+ // Animate all the tasks into place
+ requestSynchronizeStackViewsWithModel(200);
}
- // Get the stack scroll of the task to anchor to (since we are removing something, the front
- // most task will be our anchor task)
- Task anchorTask = null;
- float prevAnchorTaskScroll = 0;
- boolean pullStackForward = stack.getTaskCount() > 0;
- if (pullStackForward) {
- anchorTask = mStack.getFrontMostTask();
- prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- }
-
- // Update the min/max scroll and animate other task views into their new positions
- updateMinMaxScroll(true);
-
- if (wasFrontMostTask) {
- // Since the max scroll progress is offset from the bottom of the stack, just scroll
- // to ensure that the new front most task is now fully visible
- mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
- } else if (pullStackForward) {
- // Otherwise, offset the scroll by half the movement of the anchor task to allow the
- // tasks behind the removed task to move forward, and the tasks in front to move back
- float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- - prevAnchorTaskScroll) / 2);
- mStackScroller.boundScroll();
- }
-
- // Animate all the tasks into place
- requestSynchronizeStackViewsWithModel(200);
-
// Update the new front most task
if (newFrontMostTask != null) {
TaskView frontTv = getChildViewForTask(newFrontMostTask);
@@ -1132,11 +1091,6 @@
public void prepareViewToEnterPool(TaskView tv) {
Task task = tv.getTask();
- // Clear the accessibility focus for that view
- if (tv.isAccessibilityFocused()) {
- tv.clearAccessibilityFocus();
- }
-
// Report that this tasks's data is no longer being used
Recents.getTaskLoader().unloadTaskData(task);
@@ -1167,16 +1121,10 @@
// If the doze trigger has already fired, then update the state for this task view
tv.setNoUserInteractionState();
- // If we've finished the start animation, then ensure we always enable the focus animations
- if (mStartEnterAnimationCompleted) {
- tv.enableFocusAnimations();
- }
-
// Find the index where this task should be placed in the stack
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
if (taskIndex != -1) {
-
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -1231,13 +1179,6 @@
}
}
- @Override
- public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
- if (focused) {
- mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
- }
- }
-
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
@@ -1259,13 +1200,13 @@
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
if (removedComponents.contains(t.key.getComponent())) {
- TaskView tv = getChildViewForTask(t);
+ final TaskView tv = getChildViewForTask(t);
if (tv != null) {
// For visible children, defer removing the task until after the animation
tv.startDeleteTaskAnimation(new Runnable() {
@Override
public void run() {
- mStack.removeTask(t);
+ removeTaskViewFromStack(tv);
}
}, 0);
} else {
@@ -1276,33 +1217,23 @@
}
}
- public final void onBusEvent(DismissTaskEvent event) {
- TaskView tv = event.taskView;
- Task task = tv.getTask();
- int taskIndex = mStack.indexOfTask(task);
- boolean taskWasFocused = tv.isFocusedTask();
+ public final void onBusEvent(DismissTaskViewEvent event) {
+ removeTaskViewFromStack(event.taskView);
+ }
- // Announce for accessibility
- tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
- tv.getTask().activityLabel));
+ public final void onBusEvent(FocusNextTaskViewEvent event) {
+ setRelativeFocusedTask(true, true);
+ }
- // Remove the task from the view
- mStack.removeTask(task);
+ public final void onBusEvent(FocusPreviousTaskViewEvent event) {
+ setRelativeFocusedTask(false, true);
+ }
- // If the dismissed task was focused, then we should focus the new task in the same index
- if (taskWasFocused) {
- ArrayList<Task> tasks = mStack.getTasks();
- int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1);
- if (nextTaskIndex >= 0) {
- Task nextTask = tasks.get(nextTaskIndex);
- TaskView nextTv = getChildViewForTask(nextTask);
- if (nextTv != null) {
- // Focus the next task, and only animate the visible state if we are launched
- // from Alt-Tab
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- nextTv.setFocusedTask(launchState.launchedWithAltTab);
- }
- }
+ public final void onBusEvent(DismissFocusedTaskViewEvent event) {
+ if (mFocusedTaskIndex != -1) {
+ Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ TaskView tv = getChildViewForTask(t);
+ tv.dismissTask();
}
}
@@ -1316,4 +1247,35 @@
reset();
}
}
+
+ /**
+ * Removes the task from the stack, and updates the focus to the next task in the stack if the
+ * removed TaskView was focused.
+ */
+ private void removeTaskViewFromStack(TaskView tv) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ Task task = tv.getTask();
+ int taskIndex = mStack.indexOfTask(task);
+ boolean taskWasFocused = tv.isFocusedTask();
+
+ // Reset the previously focused task before it is removed from the stack
+ resetFocusedTask();
+
+ // Announce for accessibility
+ tv.announceForAccessibility(getContext().getString(
+ R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel));
+
+ // Remove the task from the stack
+ mStack.removeTask(task);
+
+ if (taskWasFocused || ssp.isTouchExplorationEnabled()) {
+ // If the dismissed task was focused or if we are in touch exploration mode, then focus
+ // the next task
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ setFocusedTask(taskIndex - 1,
+ !mStack.getTasks().get(taskIndex - 1).isFreeformTask() /* scrollToTask */,
+ launchState.launchedWithAltTab);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
deleted file mode 100644
index 9a5d9bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.Log;
-import com.android.systemui.R;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.ParametricCurve;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-
-/**
- * The layout logic for a TaskStackView.
- */
-public class TaskStackViewLayoutAlgorithm {
-
- private static final boolean DEBUG = false;
- private static final String TAG = "TaskStackViewLayoutAlgorithm";
-
- // The min scale of the last task at the top of the curve
- private static final float STACK_PEEK_MIN_SCALE = 0.75f;
- // The scale of the last task
- private static final float SINGLE_TASK_SCALE = 0.95f;
- // The percentage of height of task to show between tasks
- private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
-
- // A report of the visibility state of the stack
- public class VisibilityReport {
- public int numVisibleTasks;
- public int numVisibleThumbnails;
-
- /** Package level ctor */
- VisibilityReport(int tasks, int thumbnails) {
- numVisibleTasks = tasks;
- numVisibleThumbnails = thumbnails;
- }
- }
-
- Context mContext;
- RecentsConfiguration mConfig;
-
- // This is the view bounds inset exactly by the search bar, but without the bottom inset
- // see RecentsConfiguration.getTaskStackBounds()
- public Rect mStackRect = new Rect();
- // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
- // of the stack rect
- public Rect mTaskRect = new Rect();
- // This is the current system insets
- public Rect mSystemInsets = new Rect();
-
- // The smallest scroll progress, at this value, the back most task will be visible
- float mMinScrollP;
- // The largest scroll progress, at this value, the front most task will be visible above the
- // navigation bar
- float mMaxScrollP;
- // The initial progress that the scroller is set
- float mInitialScrollP;
-
- // The relative progress to ensure that the height between affiliated tasks is respected
- float mWithinAffiliationPOffset;
- // The relative progress to ensure that the height between non-affiliated tasks is
- // respected
- float mBetweenAffiliationPOffset;
- // The relative progress to ensure that the task height is respected
- float mTaskHeightPOffset;
- // The relative progress to ensure that the half task height is respected
- float mTaskHalfHeightPOffset;
- // The relative progress to ensure that the offset from the bottom of the stack to the bottom
- // of the task is respected
- float mTaskBottomPOffset;
- // The front-most task bottom offset
- int mTaskBottomOffset;
-
- // The last computed task count
- int mNumTasks;
- // The min/max z translations
- int mMinTranslationZ;
- int mMaxTranslationZ;
-
- // Optimization, allows for quick lookup of task -> progress
- HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
-
- // Log function
- static ParametricCurve sCurve;
-
- public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) {
- Resources res = context.getResources();
- mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
- mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
- mContext = context;
- mConfig = config;
- if (sCurve == null) {
- sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
- // The large the XScale, the longer the flat area of the curve
- private static final float XScale = 1.75f;
- private static final float LogBase = 3000;
-
- float reverse(float x) {
- return (-x * XScale) + 1;
- }
-
- @Override
- public float f(float x) {
- return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
- }
-
- @Override
- public float invF(float y) {
- return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
- }
- }, new ParametricCurve.ParametricCurveFunction() {
- @Override
- public float f(float p) {
- if (p < 0) return STACK_PEEK_MIN_SCALE;
- if (p > 1) return 1f;
- float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
- float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
- return scale;
- }
- });
- }
- }
-
- /**
- * Sets the system insets.
- */
- public void setSystemInsets(Rect systemInsets) {
- mSystemInsets.set(systemInsets);
- if (DEBUG) {
- Log.d(TAG, "setSystemInsets: " + systemInsets);
- }
- }
-
- /**
- * Computes the stack and task rects
- */
- public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
- int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width());
- int heightPadding = mContext.getResources().getDimensionPixelSize(
- R.dimen.recents_stack_top_padding);
-
- // Compute the stack rect, inset from the given task stack bounds
- mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
- taskStackBounds.right - widthPadding, windowHeight);
- mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
-
- // Compute the task rect, align it to the top-center square in the stack rect
- int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset);
- int xOffset = (mStackRect.width() - size) / 2;
- mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
- mStackRect.right - xOffset, mStackRect.top + size);
-
- // Compute the progress offsets
- int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
- R.dimen.recents_task_bar_height);
- int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
- mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
- mStackRect);
- mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
- mStackRect);
- mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
- mStackRect);
- mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
- mStackRect);
- mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
-
- if (DEBUG) {
- Log.d(TAG, "computeRects");
- Log.d(TAG, "\tmStackRect: " + mStackRect);
- Log.d(TAG, "\tmTaskRect: " + mTaskRect);
- Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
-
- Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
- Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
- Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
- Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
- Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset);
-
- Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
- Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
-
- for (int height = 0; height <= 1000; height += 50) {
- float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
- float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
- Log.d(TAG, "offset: " + height + ", " +
- p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
- sCurve.pToScale(1f - p) + ", " +
- p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
- }
- }
- }
-
- /**
- * Computes the minimum and maximum scroll progress values. This method may be called before
- * the RecentsConfiguration is set, so we need to pass in the alt-tab state.
- */
- void computeMinMaxScroll(ArrayList<Task> tasks) {
- if (DEBUG) {
- Log.d(TAG, "computeMinMaxScroll");
- }
-
- // Clear the progress map
- mTaskProgressMap.clear();
- mNumTasks = tasks.size();
-
- // Return early if we have no tasks
- if (tasks.isEmpty()) {
- mMinScrollP = mMaxScrollP = 0;
- return;
- }
-
- // We calculate the progress by taking the progress of the element from the bottom of the
- // screen
- if (mNumTasks == 1) {
- // Just center the task in the visible stack rect
- mMinScrollP = mMaxScrollP = mInitialScrollP = 0f;
- mTaskProgressMap.put(tasks.get(0).key, 0f);
- } else {
- // Update the tasks from back to front with the new progresses. We set the initial
- // progress to the progress at which the top of the last task is near the center of the
- // visible stack rect.
- float pAtBackMostTaskTop = 0;
- float pAtFrontMostTaskTop = pAtBackMostTaskTop;
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
-
- if (i < (taskCount - 1)) {
- // Increment the peek height
- float pPeek = task.group.isFrontMostTask(task) ?
- mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
- pAtFrontMostTaskTop += pPeek;
- }
- }
-
- // Set the max scroll progress to the point at which the bottom of the front-most task
- // is aligned to the bottom of the stack (including nav bar and stack padding)
- mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
- // Basically align the back-most task such that its progress is the same as the top of
- // the front most task at the max scroll
- mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset;
- // The offset the inital scroll position to the front of the stack, with half the front
- // task height visible
- mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
- }
- }
-
- /**
- * Computes the maximum number of visible tasks and thumbnails. Requires that
- * computeMinMaxScroll() is called first.
- */
- public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
- if (tasks.size() <= 1) {
- return new VisibilityReport(1, 1);
- }
-
- // Walk backwards in the task stack and count the number of tasks and visible thumbnails
- int taskHeight = mTaskRect.height();
- int taskBarHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.recents_task_bar_height);
- int numVisibleTasks = 1;
- int numVisibleThumbnails = 1;
- float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
- int prevScreenY = sCurve.pToX(progress, mStackRect);
- for (int i = tasks.size() - 2; i >= 0; i--) {
- Task task = tasks.get(i);
- progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
- if (progress < 0) {
- break;
- }
- boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
- if (isFrontMostTaskInGroup) {
- float scaleAtP = sCurve.pToScale(progress);
- int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
- int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
- boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
- if (hasVisibleThumbnail) {
- numVisibleThumbnails++;
- numVisibleTasks++;
- prevScreenY = screenY;
- } else {
- // Once we hit the next front most task that does not have a visible thumbnail,
- // walk through remaining visible set
- for (int j = i; j >= 0; j--) {
- numVisibleTasks++;
- progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
- if (progress < 0) {
- break;
- }
- }
- break;
- }
- } else if (!isFrontMostTaskInGroup) {
- // Affiliated task, no thumbnail
- numVisibleTasks++;
- }
- }
- return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
- }
-
- /**
- * Returns the transform for the given task. This transform is relative to the mTaskRect, which
- * is what the view is measured and laid out with.
- */
- public TaskViewTransform getStackTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- // Return early if we have an invalid index
- if (task == null || !mTaskProgressMap.containsKey(task.key)) {
- transformOut.reset();
- return transformOut;
- }
- return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
- prevTransform);
- }
-
- /** Update/get the transform */
- public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- if (DEBUG) {
- Log.d(TAG, "getStackTransform: " + stackScroll);
- }
-
- if (mNumTasks == 1) {
- // Center the task in the stack, changing the scale will not follow the curve, but just
- // modulate some values directly
- float pTaskRelative = -stackScroll;
- float scale = SINGLE_TASK_SCALE;
- int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
- transformOut.scale = scale;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
- transformOut.translationZ = mMaxTranslationZ;
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- return transformOut;
- } else {
- float pTaskRelative = taskProgress - stackScroll;
- float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
- // If the task top is outside of the bounds below the screen, then immediately reset it
- if (pTaskRelative > 1f) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- // The check for the top is trickier, since we want to show the next task if it is at
- // all visible, even if p < 0.
- if (pTaskRelative < 0f) {
- if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- }
- float scale = sCurve.pToScale(pBounded);
- int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
- transformOut.scale = scale;
- transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
- scaleYOffset;
- transformOut.translationZ = Math.max(mMinTranslationZ,
- mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- return transformOut;
- }
- }
-
- /**
- * Returns the untransformed task view bounds.
- */
- public Rect getUntransformedTaskViewBounds() {
- return new Rect(mTaskRect);
- }
-
- /**
- * Returns the scroll progress to scroll to such that the top of the task is at the top of the
- * stack.
- */
- float getStackScrollForTask(Task t) {
- if (!mTaskProgressMap.containsKey(t.key)) return 0f;
- return mTaskProgressMap.get(t.key);
- }
-}
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 3d3b13d..6b92aed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.OverScroller;
@@ -29,15 +30,21 @@
/* The scrolling logic for a TaskStackView */
public class TaskStackViewScroller {
+
+ private static final String TAG = "TaskStackViewScroller";
+ private static final boolean DEBUG = false;
+
public interface TaskStackViewScrollerCallbacks {
- public void onScrollChanged(float p);
+ void onScrollChanged(float p);
}
Context mContext;
- TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+ TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScrollerCallbacks mCb;
float mStackScrollP;
+ float mFlingDownScrollP;
+ int mFlingDownY;
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
@@ -45,7 +52,7 @@
Interpolator mLinearOutSlowInInterpolator;
- public TaskStackViewScroller(Context context, TaskStackViewLayoutAlgorithm layoutAlgorithm) {
+ public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) {
mContext = context;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
@@ -77,11 +84,6 @@
}
}
- /** Sets the current stack scroll without calling the callback. */
- void setStackScrollRaw(float s) {
- mStackScrollP = s;
- }
-
/**
* Sets the current stack scroll to the initial state when you first enter recents.
* @return whether the stack progress changed.
@@ -92,6 +94,20 @@
return Float.compare(prevStackScrollP, mStackScrollP) != 0;
}
+ /**
+ * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
+ */
+ public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
+ int overscroll) {
+ if (DEBUG) {
+ Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
+ ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
+ }
+ mFlingDownScrollP = downScrollP;
+ mFlingDownY = downY;
+ mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
+ }
+
/** Bounds the current scroll if necessary */
public boolean boundScroll() {
float curScroll = getStackScroll();
@@ -105,7 +121,8 @@
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
- return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
+ return Math.max(mLayoutAlgorithm.mMinScrollP,
+ Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
}
/** Returns the amount that the absolute value of how much the scroll is out of bounds. */
@@ -174,21 +191,20 @@
/**** OverScroller ****/
+ // TODO: Remove
+ @Deprecated
int progressToScrollRange(float p) {
return (int) (p * mLayoutAlgorithm.mStackRect.height());
}
- float scrollRangeToProgress(int s) {
- return (float) s / mLayoutAlgorithm.mStackRect.height();
- }
-
/** Called from the view draw, computes the next scroll. */
boolean computeScroll() {
if (mScroller.computeScrollOffset()) {
- float scroll = scrollRangeToProgress(mScroller.getCurrY());
- setStackScrollRaw(scroll);
- if (mCb != null) {
- mCb.onScrollChanged(scroll);
+ float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
+ float scroll = mFlingDownScrollP + deltaP;
+ setStackScroll(scroll);
+ if (DEBUG) {
+ Log.d(TAG, "computeScroll: " + scroll);
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 1274318..9e6fb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -16,7 +16,10 @@
package com.android.systemui.recents.views;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -28,35 +31,38 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.List;
/* Handles touch events for a TaskStackView. */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
- static int INACTIVE_POINTER_ID = -1;
+
+ private static final String TAG = "TaskStackViewTouchHandler";
+ private static final boolean DEBUG = false;
+
+ private static int INACTIVE_POINTER_ID = -1;
Context mContext;
TaskStackView mSv;
TaskStackViewScroller mScroller;
VelocityTracker mVelocityTracker;
+ FlingAnimationUtils mFlingAnimUtils;
+ ValueAnimator mScrollFlingAnimator;
boolean mIsScrolling;
-
- float mInitialP;
- float mLastP;
- float mTotalPMotion;
- int mInitialMotionX, mInitialMotionY;
- int mLastMotionX, mLastMotionY;
+ float mDownScrollP;
+ int mDownX, mDownY;
int mActivePointerId = INACTIVE_POINTER_ID;
+ int mOverscrollSize;
TaskView mActiveTaskView = null;
int mMinimumVelocity;
int mMaximumVelocity;
// The scroll touch slop is used to calculate when we start scrolling
int mScrollTouchSlop;
- // The page touch slop is used to calculate when we start swiping
- float mPagingTouchSlop;
// Used to calculate when a tap is outside a task view rectangle.
final int mWindowTouchSlop;
@@ -65,18 +71,21 @@
public TaskStackViewTouchHandler(Context context, TaskStackView sv,
TaskStackViewScroller scroller) {
- mContext = context;
+ Resources res = context.getResources();
ViewConfiguration configuration = ViewConfiguration.get(context);
+ mContext = context;
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mScrollTouchSlop = configuration.getScaledTouchSlop();
- mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
+ mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
- float densityScale = context.getResources().getDisplayMetrics().density;
- mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, mPagingTouchSlop);
+ float densityScale = res.getDisplayMetrics().density;
+ mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
+ mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale,
+ configuration.getScaledPagingTouchSlop());
mSwipeHelper.setMinAlpha(1f);
}
@@ -88,11 +97,6 @@
mVelocityTracker.clear();
}
}
- void initVelocityTrackerIfNotExists() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -115,175 +119,71 @@
return null;
}
- /** Constructs a simulated motion event for the current stack scroll. */
- MotionEvent createMotionEventForStackScroll(MotionEvent ev) {
- MotionEvent pev = MotionEvent.obtainNoHistory(ev);
- pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
- return pev;
- }
-
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
- // Return early if we have no children
- boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
- if (!hasTaskViews) {
- return false;
- }
-
// Pass through to swipe helper if we are swiping
mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
return true;
}
- TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
- boolean wasScrolling = mScroller.isScrolling() ||
- (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
- int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- // Save the touch down info
- mInitialMotionX = mLastMotionX = (int) ev.getX();
- mInitialMotionY = mLastMotionY = (int) ev.getY();
- mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
- layoutAlgorithm.mStackRect);
- mActivePointerId = ev.getPointerId(0);
- mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
- // Stop the current scroll if it is still flinging
- mScroller.stopScroller();
- mScroller.stopBoundScrollAnimation();
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
- break;
- }
- case MotionEvent.ACTION_POINTER_DOWN: {
- final int index = ev.getActionIndex();
- mActivePointerId = ev.getPointerId(index);
- mLastMotionX = (int) ev.getX(index);
- mLastMotionY = (int) ev.getY(index);
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
- // Initialize the velocity tracker if necessary
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-
- int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- int y = (int) ev.getY(activePointerIndex);
- int x = (int) ev.getX(activePointerIndex);
- if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
- // Save the touch move info
- mIsScrolling = true;
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
-
- mLastMotionX = x;
- mLastMotionY = y;
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
- break;
- }
- case MotionEvent.ACTION_POINTER_UP: {
- int pointerIndex = ev.getActionIndex();
- int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // Select a new active pointer id and reset the motion state
- final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
- mActivePointerId = ev.getPointerId(newPointerIndex);
- mLastMotionX = (int) ev.getX(newPointerIndex);
- mLastMotionY = (int) ev.getY(newPointerIndex);
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
- mVelocityTracker.clear();
- }
- break;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP: {
- // Animate the scroll back if we've cancelled
- mScroller.animateBoundScroll();
- // Reset the drag state and the velocity tracker
- mIsScrolling = false;
- mActivePointerId = INACTIVE_POINTER_ID;
- mActiveTaskView = null;
- mTotalPMotion = 0;
- recycleVelocityTracker();
- break;
- }
- }
-
- return wasScrolling || mIsScrolling;
+ return handleTouchEvent(ev);
}
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
- // Short circuit if we have no children
- boolean hasTaskViews = (mSv.getTaskViews().size() > 0);
- if (!hasTaskViews) {
- return false;
- }
-
// Pass through to swipe helper if we are swiping
if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
return true;
}
- // Update the velocity tracker
- initVelocityTrackerIfNotExists();
+ handleTouchEvent(ev);
+ return true;
+ }
- TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
+ private boolean handleTouchEvent(MotionEvent ev) {
+ // Short circuit if we have no children
+ if (mSv.getTaskViews().size() == 0) {
+ return false;
+ }
+
+ final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
// Save the touch down info
- mInitialMotionX = mLastMotionX = (int) ev.getX();
- mInitialMotionY = mLastMotionY = (int) ev.getY();
- mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
- layoutAlgorithm.mStackRect);
+ mDownX = (int) ev.getX();
+ mDownY = (int) ev.getY();
+ mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(0);
- mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+ mActiveTaskView = findViewAtPoint(mDownX, mDownY);
+
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
+ Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
+
// Initialize the velocity tracker
initOrResetVelocityTracker();
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
+ mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
+ mDownX = (int) ev.getX();
+ mDownY = (int) ev.getY();
+ mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(index);
- mLastMotionX = (int) ev.getX(index);
- mLastMotionY = (int) ev.getY(index);
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY,
- layoutAlgorithm.mStackRect);
+ mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
- if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
- mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
- int yTotal = Math.abs(y - mInitialMotionY);
- float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect);
- float deltaP = mLastP - curP;
if (!mIsScrolling) {
- if (yTotal > mScrollTouchSlop) {
+ if (Math.abs(y - mDownY) > mScrollTouchSlop) {
mIsScrolling = true;
+
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -292,54 +192,17 @@
}
}
if (mIsScrolling) {
- float curStackScroll = mScroller.getStackScroll();
- float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
- if (Float.compare(overScrollAmount, 0f) != 0) {
- // Bound the overscroll to a fixed amount, and inversely scale the y-movement
- // relative to how close we are to the max overscroll
- float maxOverScroll = mContext.getResources().getFloat(
- R.dimen.recents_stack_overscroll_percentage);
- deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
- / maxOverScroll));
+ // If we just move linearly on the screen, then that would map to 1/arclength
+ // of the curve, so just move the scroll proportional to that
+ float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
+ float curScrollP = mDownScrollP + deltaP;
+ mScroller.setStackScroll(curScrollP);
+ if (DEBUG) {
+ Log.d(TAG, "scroll: " + curScrollP);
}
- mScroller.setStackScroll(curStackScroll + deltaP);
- }
- mLastMotionX = x;
- mLastMotionY = y;
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
- mTotalPMotion += Math.abs(deltaP);
- break;
- }
- case MotionEvent.ACTION_UP: {
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
- if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
- float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);
- int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *
- (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange -
- Constants.Values.TaskStackView.TaskStackMinOverscrollRange));
- mScroller.mScroller.fling(0,
- mScroller.progressToScrollRange(mScroller.getStackScroll()),
- 0, velocity,
- 0, 0,
- mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
- mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP),
- 0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange +
- overscrollRange);
- // Invalidate to kick off computeScroll
- mSv.invalidate();
- } else if (mIsScrolling && mScroller.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
- mScroller.animateBoundScroll();
- } else if (mActiveTaskView == null) {
- // This tap didn't start on a task.
- maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
}
- mActivePointerId = INACTIVE_POINTER_ID;
- mIsScrolling = false;
- mTotalPMotion = 0;
- recycleVelocityTracker();
+ mVelocityTracker.addMovement(ev);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
@@ -349,34 +212,91 @@
// Select a new active pointer id and reset the motion state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
- mLastMotionX = (int) ev.getX(newPointerIndex);
- mLastMotionY = (int) ev.getY(newPointerIndex);
- mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect);
- mVelocityTracker.clear();
}
+ mVelocityTracker.addMovement(ev);
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.addMovement(ev);
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int y = (int) ev.getY(activePointerIndex);
+ int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+ float curScrollP = mScroller.getStackScroll();
+ if (mIsScrolling) {
+ boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
+ if (hasFreeformTasks && velocity > 0 &&
+ curScrollP > layoutAlgorithm.mStackEndScrollP) {
+ // Snap to workspace
+ float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mPreferredStackEndScrollP);
+ mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+ mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+ (Integer) animation.getAnimatedValue());
+ float scroll = mDownScrollP + deltaP;
+ mScroller.setStackScroll(scroll);
+ }
+ });
+ mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+ mScrollFlingAnimator.start();
+ } else if (hasFreeformTasks && velocity < 0 &&
+ curScrollP > (layoutAlgorithm.mStackEndScrollP -
+ layoutAlgorithm.mTaskHalfHeightPOffset)) {
+ // Snap to stack
+ float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mMaxScrollP);
+ mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+ mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+ (Integer) animation.getAnimatedValue());
+ float scroll = mDownScrollP + deltaP;
+ mScroller.setStackScroll(scroll);
+ }
+ });
+ mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+ mScrollFlingAnimator.start();
+ } else if (mScroller.isScrollOutOfBounds()) {
+ mScroller.animateBoundScroll();
+ } else if (Math.abs(velocity) > mMinimumVelocity) {
+ float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mPreferredStackEndScrollP);
+ float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mMinScrollP);
+ mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
+ mOverscrollSize);
+ mSv.invalidate();
+ }
+ } else if (mActiveTaskView == null) {
+ // This tap didn't start on a task.
+ maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
+ }
+
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mIsScrolling = false;
+ recycleVelocityTracker();
break;
}
case MotionEvent.ACTION_CANCEL: {
- if (mScroller.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
- mScroller.animateBoundScroll();
- }
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
- mTotalPMotion = 0;
recycleVelocityTracker();
break;
}
}
- return true;
+ return mIsScrolling;
}
/** Hides recents if the up event at (x, y) is a tap on the background area. */
void maybeHideRecentsFromBackgroundTap(int x, int y) {
// Ignore the up event if it's too far from its start position. The user might have been
// trying to scroll or swipe.
- int dx = Math.abs(mInitialMotionX - x);
- int dy = Math.abs(mInitialMotionY - y);
+ int dx = Math.abs(mDownX - x);
+ int dy = Math.abs(mDownY - y);
if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
return;
}
@@ -408,13 +328,9 @@
// Find the front most task and scroll the next task to the front
float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vScroll > 0) {
- if (mSv.ensureFocusedTask(true)) {
- mSv.focusNextTask(true, false);
- }
+ mSv.setRelativeFocusedTask(true, false /* animated */);
} else {
- if (mSv.ensureFocusedTask(true)) {
- mSv.focusNextTask(false, false);
- }
+ mSv.setRelativeFocusedTask(false, false /* animated */);
}
return true;
}
@@ -437,6 +353,7 @@
@Override
public void onBeginDrag(View v) {
TaskView tv = (TaskView) v;
+ mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX());
// Disable clipping with the stack while we are swiping
tv.setClipViewInStack(false);
// Disallow touch events from this task view
@@ -461,7 +378,7 @@
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
// Remove the task view from the stack
- EventBus.getDefault().send(new DismissTaskEvent(tv.getTask(), tv));
+ EventBus.getDefault().send(new DismissTaskViewEvent(tv.getTask(), tv));
// Keep track of deletions by keyboard
MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
Constants.Metrics.DismissSourceSwipeGesture);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index bab4da7..57cb599 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,8 +29,8 @@
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -40,13 +40,15 @@
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -55,15 +57,15 @@
public class TaskView extends FrameLayout implements Task.TaskCallbacks,
View.OnClickListener, View.OnLongClickListener {
+ private final static String TAG = "TaskView";
+ private final static boolean DEBUG = false;
+
/** The TaskView callbacks */
interface TaskViewCallbacks {
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
public void onTaskViewClipStateChanged(TaskView tv);
- public void onTaskViewFocusChanged(TaskView tv, boolean focused);
}
- RecentsConfiguration mConfig;
-
float mTaskProgress;
ObjectAnimator mTaskProgressAnimator;
float mMaxDimScale;
@@ -76,6 +78,7 @@
Task mTask;
boolean mTaskDataLoaded;
boolean mIsFocused;
+ boolean mIsFocusAnimated;
boolean mFocusAnimationsEnabled;
boolean mClipViewInStack;
AnimateableViewBounds mViewBounds;
@@ -116,8 +119,8 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ RecentsConfiguration config = Recents.getConfiguration();
Resources res = context.getResources();
- mConfig = RecentsConfiguration.getInstance();
mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
mClipViewInStack = true;
mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
@@ -130,8 +133,8 @@
com.android.internal.R.interpolator.decelerate_quint);
setTaskProgress(getTaskProgress());
setDim(getDim());
- if (mConfig.fakeShadows) {
- setBackground(new FakeShadowDrawable(res, mConfig));
+ if (config.fakeShadows) {
+ setBackground(new FakeShadowDrawable(res, config));
}
setOutlineProvider(mViewBounds);
}
@@ -219,9 +222,11 @@
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
ValueAnimator.AnimatorUpdateListener updateCallback) {
+ RecentsConfiguration config = Recents.getConfiguration();
+
// Apply the transform
toTransform.applyToTaskView(this, duration, mFastOutSlowInInterpolator, false,
- !mConfig.fakeShadows, updateCallback);
+ !config.fakeShadows, updateCallback);
// Update the task progress
Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);
@@ -272,7 +277,8 @@
* first layout because the actual animation into recents may take a long time. */
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
boolean occludesLaunchTarget, int offscreenY) {
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
int initialDim = getDim();
if (launchState.launchedHasConfigurationChanged) {
// Just load the views as-is
@@ -302,7 +308,8 @@
/** Animates this task view as it enters recents */
void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
Resources res = mContext.getResources();
final TaskViewTransform transform = ctx.currentTaskTransform;
final int transitionEnterFromAppDelay = res.getInteger(
@@ -317,7 +324,6 @@
R.integer.recents_task_enter_from_home_stagger_delay);
final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
R.dimen.recents_task_view_affiliate_group_enter_offset);
- int startDelay = 0;
if (launchState.launchedFromAppWithThumbnail) {
if (mTask.isLaunchTarget) {
@@ -366,7 +372,6 @@
ctx.postAnimationTrigger.increment();
}
}
- startDelay = transitionEnterFromAppDelay;
} else if (launchState.launchedFromHome) {
// Animate the tasks up
@@ -376,7 +381,7 @@
setScaleX(transform.scale);
setScaleY(transform.scale);
- if (!mConfig.fakeShadows) {
+ if (!config.fakeShadows) {
animate().translationZ(transform.translationZ);
}
animate()
@@ -395,17 +400,7 @@
})
.start();
ctx.postAnimationTrigger.increment();
- startDelay = delay;
}
-
- // Enable the focus animations from this point onwards so that they aren't affected by the
- // window transitions
- postDelayed(new Runnable() {
- @Override
- public void run() {
- enableFocusAnimations();
- }
- }, startDelay);
}
public void fadeInActionButton(int delay, int duration) {
@@ -547,7 +542,7 @@
startDeleteTaskAnimation(new Runnable() {
@Override
public void run() {
- EventBus.getDefault().send(new DismissTaskEvent(mTask, tv));
+ EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv));
}
}, 0);
}
@@ -557,7 +552,11 @@
* view.
*/
boolean shouldClipViewInStack() {
- return mClipViewInStack && (getVisibility() == View.VISIBLE);
+ // Never clip for freeform tasks or if invisible
+ if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ return mClipViewInStack;
}
/** Sets whether this view should be clipped, or clipped against. */
@@ -584,8 +583,10 @@
/** Returns the current dim. */
public void setDim(int dim) {
+ RecentsConfiguration config = Recents.getConfiguration();
+
mDimAlpha = dim;
- if (mConfig.useHardwareLayers) {
+ if (config.useHardwareLayers) {
// Defer setting hardware layers if we have not yet measured, or there is no dim to draw
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0));
@@ -620,6 +621,8 @@
anim.addListener(postAnimRunnable);
}
anim.start();
+ } else {
+ postAnimRunnable.onAnimationEnd(null);
}
}
@@ -641,58 +644,32 @@
/**** View focus state ****/
/**
- * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
- * if the view is not currently visible, or we are in touch state (where we still want to keep
- * track of focus).
+ * Explicitly sets the focused state of this task.
*/
- public void setFocusedTask(boolean animateFocusedState) {
- mIsFocused = true;
- if (mFocusAnimationsEnabled) {
- // Focus the header bar
- mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
- }
- // Update the thumbnail alpha with the focus
- mThumbnailView.onFocusChanged(true);
- // Call the callback
- if (mCb != null) {
- mCb.onTaskViewFocusChanged(this, true);
- }
- // Workaround, we don't always want it focusable in touch mode, but we want the first task
- // to be focused after the enter-recents animation, which can be triggered from either touch
- // or keyboard
- setFocusableInTouchMode(true);
- requestFocus();
- setFocusableInTouchMode(false);
- invalidate();
- }
-
- /**
- * Unsets the focused task explicitly.
- */
- void unsetFocusedTask() {
- mIsFocused = false;
- if (mFocusAnimationsEnabled) {
- // Un-focus the header bar
- mHeaderView.onTaskViewFocusChanged(false, true);
+ public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) {
+ if (DEBUG) {
+ Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused +
+ " mIsFocused: " + mIsFocused + " animated: " + animated +
+ " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() +
+ " isAccessibilityFocused(): " + isAccessibilityFocused());
}
- // Update the thumbnail alpha with the focus
- mThumbnailView.onFocusChanged(false);
- // Call the callback
- if (mCb != null) {
- mCb.onTaskViewFocusChanged(this, false);
- }
- invalidate();
- }
-
- /**
- * Updates the explicitly focused state when the view focus changes.
- */
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (!gainFocus) {
- unsetFocusedTask();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ mIsFocused = isFocused;
+ mIsFocusAnimated = animated;
+ mHeaderView.onTaskViewFocusChanged(isFocused, animated);
+ mThumbnailView.onFocusChanged(isFocused);
+ if (isFocused) {
+ if (requestViewFocus && !isFocused()) {
+ requestFocus();
+ }
+ if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+ requestAccessibilityFocus();
+ }
+ } else {
+ if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) {
+ clearAccessibilityFocus();
+ }
}
}
@@ -700,17 +677,14 @@
* Returns whether we have explicitly been focused.
*/
public boolean isFocusedTask() {
- return mIsFocused || isFocused();
+ return mIsFocused;
}
- /** Enables all focus animations. */
- void enableFocusAnimations() {
- boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
- mFocusAnimationsEnabled = true;
- if (mIsFocused && !wasFocusAnimationsEnabled) {
- // Re-notify the header if we were focused and animations were not previously enabled
- mHeaderView.onTaskViewFocusChanged(true, true);
- }
+ /**
+ * Returns whether this focused task is animated.
+ */
+ public boolean isFocusAnimated() {
+ return mIsFocusAnimated;
}
public void disableLayersForOneFrame() {
@@ -734,13 +708,14 @@
@Override
public void onTaskDataLoaded() {
+ RecentsConfiguration config = Recents.getConfiguration();
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
mActionButtonView.setOnClickListener(this);
- setOnLongClickListener(mConfig.hasDockedTasks ? null : this);
+ setOnLongClickListener(config.hasDockedTasks ? null : this);
}
mTaskDataLoaded = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e1e07ef..649199e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -39,7 +39,6 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -244,9 +243,8 @@
mMoveTaskButton.setOnClickListener(this);
// In accessibility, a single click on the focused app info button will show it
- AccessibilityManager am = (AccessibilityManager) getContext().
- getSystemService(Context.ACCESSIBILITY_SERVICE);
- if (am != null && am.isEnabled()) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isTouchExplorationEnabled()) {
mApplicationIcon.setOnClickListener(this);
}
}
@@ -257,6 +255,10 @@
mApplicationIcon.setImageDrawable(null);
mApplicationIcon.setOnClickListener(null);
mMoveTaskButton.setOnClickListener(null);
+
+ // Stop any focus animations
+ Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+ mBackground.jumpToCurrentState();
}
/** Updates the resize task bar button. */
@@ -369,16 +371,17 @@
/** Notifies the associated TaskView has been focused. */
void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
- // If we are not animating the visible state, just return
- if (!animateFocusedState) return;
-
boolean isRunning = false;
if (mFocusAnimator != null) {
isRunning = mFocusAnimator.isRunning();
- Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
}
+ Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+ mBackground.jumpToCurrentState();
if (focused) {
+ // If we are not animating the visible state, just return
+ if (!animateFocusedState) return;
+
int currentColor = mBackgroundColor;
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
int[][] states = new int[][] {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index ec50eb6..174ff33 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -26,6 +26,7 @@
/* The transform state for a task view */
public class TaskViewTransform {
public int startDelay = 0;
+ public int translationX = 0;
public int translationY = 0;
public float translationZ = 0;
public float scale = 1f;
@@ -43,6 +44,7 @@
public TaskViewTransform(TaskViewTransform o) {
startDelay = o.startDelay;
+ translationX = o.translationX;
translationY = o.translationY;
translationZ = o.translationZ;
scale = o.scale;
@@ -52,9 +54,12 @@
p = o.p;
}
- /** Resets the current transform */
+ /**
+ * Resets the current transform.
+ */
public void reset() {
startDelay = 0;
+ translationX = 0;
translationY = 0;
translationZ = 0;
scale = 1f;
@@ -71,6 +76,9 @@
public boolean hasScaleChangedFrom(float v) {
return (Float.compare(scale, v) != 0);
}
+ public boolean hasTranslationXChangedFrom(float v) {
+ return (Float.compare(translationX, v) != 0);
+ }
public boolean hasTranslationYChangedFrom(float v) {
return (Float.compare(translationY, v) != 0);
}
@@ -87,6 +95,9 @@
boolean requiresLayers = false;
// Animate to the final state
+ if (hasTranslationXChangedFrom(v.getTranslationX())) {
+ anim.translationX(translationX);
+ }
if (hasTranslationYChangedFrom(v.getTranslationY())) {
anim.translationY(translationY);
}
@@ -117,6 +128,9 @@
.start();
} else {
// Set the changed properties
+ if (hasTranslationXChangedFrom(v.getTranslationX())) {
+ v.setTranslationX(translationX);
+ }
if (hasTranslationYChangedFrom(v.getTranslationY())) {
v.setTranslationY(translationY);
}
@@ -147,7 +161,8 @@
@Override
public String toString() {
- return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +
+ return "TaskViewTransform delay: " + startDelay +
+ " x: " + translationX + " y: " + translationY + " z: " + translationZ +
" scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
" p: " + p;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 8938669..2c16f81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -217,7 +217,7 @@
private boolean mDeviceProvisioned = false;
- private RecentsComponent mRecents;
+ protected RecentsComponent mRecents;
protected int mZenMode;
@@ -1275,20 +1275,22 @@
int maxHeight = mRowMaxHeight;
final StatusBarNotification sbn = entry.notification;
- RemoteViews contentView = sbn.getNotification().contentView;
- RemoteViews bigContentView = sbn.getNotification().bigContentView;
- RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;
+ entry.cacheContentViews(mContext, null);
+
+ final RemoteViews contentView = entry.cachedContentView;
+ final RemoteViews bigContentView = entry.cachedBigContentView;
+ final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+ final RemoteViews publicContentView = entry.cachedPublicContentView;
if (contentView == null) {
+ Log.v(TAG, "no contentView for: " + sbn.getNotification());
return false;
}
if (DEBUG) {
- Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
+ Log.v(TAG, "publicContentView: " + publicContentView);
}
- Notification publicNotification = sbn.getNotification().publicVersion;
-
ExpandableNotificationRow row;
// Stash away previous user expansion state so we can restore it at
@@ -1377,9 +1379,9 @@
// now the public version
View publicViewLocal = null;
- if (publicNotification != null) {
+ if (publicContentView != null) {
try {
- publicViewLocal = publicNotification.contentView.apply(
+ publicViewLocal = publicContentView.apply(
sbn.getPackageContext(mContext),
contentContainerPublic, mOnClickHandler);
@@ -1537,30 +1539,9 @@
}
if (viableAction != null) {
- Notification stripped = n.clone();
- Notification.Builder.stripForDelivery(stripped);
- stripped.extras.putBoolean("android.rebuild", true);
- stripped.actions = new Notification.Action[] { viableAction };
- stripped.extras.putBoolean("android.rebuild.contentView", true);
- stripped.contentView = null;
- stripped.extras.putBoolean("android.rebuild.bigView", true);
- stripped.bigContentView = null;
- stripped.extras.putBoolean("android.rebuild.hudView", true);
- stripped.headsUpContentView = null;
-
- stripped.extras.putParcelable(Notification.EXTRA_LARGE_ICON,
- stripped.getLargeIcon());
- if (SystemProperties.getBoolean("debug.strip_third_line", false)) {
- stripped.extras.putCharSequence(Notification.EXTRA_INFO_TEXT, null);
- stripped.extras.putCharSequence(Notification.EXTRA_SUMMARY_TEXT, null);
- }
-
- Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
-
- n.actions = rebuilt.actions;
- n.bigContentView = rebuilt.bigContentView;
- n.headsUpContentView = rebuilt.headsUpContentView;
- n.publicVersion = rebuilt.publicVersion;
+ Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
+ rebuilder.setActions(viableAction);
+ rebuilder.build(); // will rewrite n
}
}
}
@@ -2034,12 +2015,15 @@
}
Notification n = notification.getNotification();
- if (DEBUG) {
- logUpdate(entry, n);
- }
- boolean applyInPlace = shouldApplyInPlace(entry, n);
+
+ boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
boolean shouldInterrupt = shouldInterrupt(entry, notification);
boolean alertAgain = alertAgain(entry, n);
+ if (DEBUG) {
+ Log.d(TAG, "applyInPlace=" + applyInPlace
+ + " shouldInterrupt=" + shouldInterrupt
+ + " alertAgain=" + alertAgain);
+ }
entry.notification = notification;
mGroupManager.onEntryUpdated(entry, entry.notification);
@@ -2104,101 +2088,32 @@
protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
boolean alertAgain);
- private void logUpdate(Entry oldEntry, Notification n) {
- StatusBarNotification oldNotification = oldEntry.notification;
- Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
- + " ongoing=" + oldNotification.isOngoing()
- + " expanded=" + oldEntry.getContentView()
- + " contentView=" + oldNotification.getNotification().contentView
- + " bigContentView=" + oldNotification.getNotification().bigContentView
- + " publicView=" + oldNotification.getNotification().publicVersion
- + " rowParent=" + oldEntry.row.getParent());
- Log.d(TAG, "new notification: when=" + n.when
- + " ongoing=" + oldNotification.isOngoing()
- + " contentView=" + n.contentView
- + " bigContentView=" + n.bigContentView
- + " publicView=" + n.publicVersion);
- }
-
- /**
- * @return whether we can just reapply the RemoteViews from a notification in-place when it is
- * updated
- */
- private boolean shouldApplyInPlace(Entry entry, Notification n) {
- StatusBarNotification oldNotification = entry.notification;
- // XXX: modify when we do something more intelligent with the two content views
- final RemoteViews oldContentView = oldNotification.getNotification().contentView;
- final RemoteViews contentView = n.contentView;
- final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
- final RemoteViews bigContentView = n.bigContentView;
- final RemoteViews oldHeadsUpContentView
- = oldNotification.getNotification().headsUpContentView;
- final RemoteViews headsUpContentView = n.headsUpContentView;
- final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
- final RemoteViews oldPublicContentView = oldPublicNotification != null
- ? oldPublicNotification.contentView : null;
- final Notification publicNotification = n.publicVersion;
- final RemoteViews publicContentView = publicNotification != null
- ? publicNotification.contentView : null;
- boolean contentsUnchanged = entry.getContentView() != null
- && contentView.getPackage() != null
- && oldContentView.getPackage() != null
- && oldContentView.getPackage().equals(contentView.getPackage())
- && oldContentView.getLayoutId() == contentView.getLayoutId();
- // large view may be null
- boolean bigContentsUnchanged =
- (entry.getExpandedContentView() == null && bigContentView == null)
- || ((entry.getExpandedContentView() != null && bigContentView != null)
- && bigContentView.getPackage() != null
- && oldBigContentView.getPackage() != null
- && oldBigContentView.getPackage().equals(bigContentView.getPackage())
- && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
- boolean headsUpContentsUnchanged =
- (oldHeadsUpContentView == null && headsUpContentView == null)
- || ((oldHeadsUpContentView != null && headsUpContentView != null)
- && headsUpContentView.getPackage() != null
- && oldHeadsUpContentView.getPackage() != null
- && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
- && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
- boolean publicUnchanged =
- (oldPublicContentView == null && publicContentView == null)
- || ((oldPublicContentView != null && publicContentView != null)
- && publicContentView.getPackage() != null
- && oldPublicContentView.getPackage() != null
- && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
- && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
- return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
- && publicUnchanged;
- }
-
- private void updateNotificationViews(Entry entry, StatusBarNotification notification) {
- final RemoteViews contentView = notification.getNotification().contentView;
- final RemoteViews bigContentView = notification.getNotification().bigContentView;
- final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
- final Notification publicVersion = notification.getNotification().publicVersion;
- final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
- : null;
+ private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
+ final RemoteViews contentView = entry.cachedContentView;
+ final RemoteViews bigContentView = entry.cachedBigContentView;
+ final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+ final RemoteViews publicContentView = entry.cachedPublicContentView;
// Reapply the RemoteViews
contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
if (bigContentView != null && entry.getExpandedContentView() != null) {
- bigContentView.reapply(notification.getPackageContext(mContext),
+ bigContentView.reapply(sbn.getPackageContext(mContext),
entry.getExpandedContentView(),
mOnClickHandler);
}
View headsUpChild = entry.getHeadsUpContentView();
if (headsUpContentView != null && headsUpChild != null) {
- headsUpContentView.reapply(notification.getPackageContext(mContext),
+ headsUpContentView.reapply(sbn.getPackageContext(mContext),
headsUpChild, mOnClickHandler);
}
if (publicContentView != null && entry.getPublicContentView() != null) {
- publicContentView.reapply(notification.getPackageContext(mContext),
+ publicContentView.reapply(sbn.getPackageContext(mContext),
entry.getPublicContentView(), mOnClickHandler);
}
// update the contentIntent
- mNotificationClicker.register(entry.row, notification);
+ mNotificationClicker.register(entry.row, sbn);
- entry.row.setStatusBarNotification(notification);
+ entry.row.setStatusBarNotification(sbn);
entry.row.notifyContentUpdated();
entry.row.resetHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index aedae52..6a90d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.content.Context;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -24,6 +25,7 @@
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.view.View;
+import android.widget.RemoteViews;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -53,6 +55,10 @@
public boolean legacy; // whether the notification has a legacy, dark background
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
+ public RemoteViews cachedContentView;
+ public RemoteViews cachedBigContentView;
+ public RemoteViews cachedHeadsUpContentView;
+ public RemoteViews cachedPublicContentView;
public Entry(StatusBarNotification n, StatusBarIconView ic) {
this.key = n.getKey();
@@ -98,6 +104,67 @@
return row.getPublicLayout().getContractedChild();
}
+ public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+ boolean cached = false;
+ if (updatedNotification != null) {
+ final Notification.Builder updatedNotificationBuilder
+ = Notification.Builder.recoverBuilder(ctx, updatedNotification);
+ final RemoteViews newContentView = updatedNotificationBuilder.makeContentView();
+ if (!compareRemoteViews(cachedContentView, newContentView)) {
+ cachedContentView = newContentView;
+ cached |= true;
+ }
+ final RemoteViews newBigContentView =
+ updatedNotificationBuilder.makeBigContentView();
+ if (!compareRemoteViews(cachedBigContentView, newBigContentView)) {
+ cachedBigContentView = newBigContentView;
+ cached |= true;
+ }
+ final RemoteViews newHeadsUpContentView =
+ updatedNotificationBuilder.makeHeadsUpContentView();
+ if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) {
+ cachedHeadsUpContentView = newHeadsUpContentView;
+ cached |= true;
+ }
+ final Notification updatedPublicNotification = updatedNotification.publicVersion;
+ final RemoteViews newPubContentView = (updatedPublicNotification != null)
+ ? Notification.Builder.recoverBuilder(
+ ctx, updatedPublicNotification).makeContentView()
+ : null;
+ if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) {
+ cachedPublicContentView = newPubContentView;
+ cached |= true;
+ }
+ } else {
+ final Notification.Builder builder
+ = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
+
+ cachedContentView = builder.makeContentView();
+ cachedBigContentView = builder.makeBigContentView();
+ cachedHeadsUpContentView = builder.makeHeadsUpContentView();
+
+ final Notification publicNotification =
+ notification.getNotification().publicVersion;
+ if (publicNotification != null) {
+ final Notification.Builder publicBuilder
+ = Notification.Builder.recoverBuilder(ctx, publicNotification);
+ cachedPublicContentView = publicBuilder.makeContentView();
+ }
+ cached = true;
+ }
+ return cached;
+ }
+
+ // Returns true if the RemoteViews are the same.
+ private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
+ return (a == null && b == null) ||
+ (a != null && b != null
+ && b.getPackage() != null
+ && a.getPackage() != null
+ && a.getPackage().equals(b.getPackage())
+ && a.getLayoutId() == b.getLayoutId());
+ }
+
public void notifyFullScreenIntentLaunched() {
lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
}
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 cfde791..f3658eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -88,7 +89,6 @@
import android.view.ViewStub;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
@@ -146,7 +146,6 @@
import com.android.systemui.statusbar.policy.FullscreenUserSwitcher;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
@@ -248,15 +247,25 @@
* Prudently disable QS and notifications. */
private static final boolean ONLY_CORE_APPS;
+ /* If true, the device supports freeform window management.
+ * This affects the status bar UI. */
+ private static final boolean FREEFORM_WINDOW_MANAGEMENT;
+
static {
boolean onlyCoreApps;
+ boolean freeformWindowManagement;
try {
- onlyCoreApps = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
- .isOnlyCoreApps();
+ IPackageManager packageManager =
+ IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ onlyCoreApps = packageManager.isOnlyCoreApps();
+ freeformWindowManagement = packageManager.hasSystemFeature(
+ PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
} catch (RemoteException e) {
onlyCoreApps = false;
+ freeformWindowManagement = false;
}
ONLY_CORE_APPS = onlyCoreApps;
+ FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
}
PhoneStatusBarPolicy mIconPolicy;
@@ -984,8 +993,8 @@
if (shelfOverride != DEFAULT) {
return shelfOverride != 0;
}
- // Otherwise default to the build setting.
- return mContext.getResources().getBoolean(R.bool.config_enableAppShelf);
+ // Otherwise default to the platform feature.
+ return FREEFORM_WINDOW_MANAGEMENT;
}
private void clearAllNotifications() {
@@ -1115,13 +1124,22 @@
}
};
- private long mLastLockToAppLongPress;
- private View.OnLongClickListener mLongPressBackRecentsListener =
- new View.OnLongClickListener() {
+ private View.OnLongClickListener mLongPressBackListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
- handleLongPressBackRecents(v);
- return true;
+ return handleLongPressBack();
+ }
+ };
+
+ private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (mRecents != null) {
+ mRecents.dockTopTask();
+ return true;
+ }
+ return false;
}
};
@@ -1170,9 +1188,9 @@
mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
mNavigationBarView.getRecentsButton().setLongClickable(true);
- mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);
+ mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener);
mNavigationBarView.getBackButton().setLongClickable(true);
- mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
+ mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener);
mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
mAssistManager.onConfigurationChanged();
@@ -4048,7 +4066,7 @@
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
- mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */);
+ mVibrator.vibrate(new long[]{0, 750L}, -1 /* repeat */);
}
public void onScreenTurnedOn() {
@@ -4057,58 +4075,22 @@
}
/**
- * This handles long-press of both back and recents. They are
- * handled together to capture them both being long-pressed
- * at the same time to exit screen pinning (lock task).
- *
- * When accessibility mode is on, only a long-press from recents
- * is required to exit.
- *
- * In all other circumstances we try to pass through long-press events
- * for Back, so that apps can still use it. Which can be from two things.
- * 1) Not currently in screen pinning (lock task).
- * 2) Back is long-pressed without recents.
+ * Handles long press for back button. This exits screen pinning.
*/
- private void handleLongPressBackRecents(View v) {
+ private boolean handleLongPressBack() {
try {
- boolean sendBackLongPress = false;
IActivityManager activityManager = ActivityManagerNative.getDefault();
- boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled();
- if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) {
- long time = System.currentTimeMillis();
- // If we recently long-pressed the other button then they were
- // long-pressed 'together'
- if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
- activityManager.stopLockTaskModeOnCurrent();
- // When exiting refresh disabled flags.
- mNavigationBarView.setDisabledFlags(mDisabled1, true);
- } else if ((v.getId() == R.id.back)
- && !mNavigationBarView.getRecentsButton().isPressed()) {
- // If we aren't pressing recents right now then they presses
- // won't be together, so send the standard long-press action.
- sendBackLongPress = true;
- }
- mLastLockToAppLongPress = time;
- } else {
- // If this is back still need to handle sending the long-press event.
- if (v.getId() == R.id.back) {
- sendBackLongPress = true;
- } else if (isAccessiblityEnabled && activityManager.isInLockTaskMode()) {
- // When in accessibility mode a long press that is recents (not back)
- // should stop lock task.
- activityManager.stopLockTaskModeOnCurrent();
- // When exiting refresh disabled flags.
- mNavigationBarView.setDisabledFlags(mDisabled1, true);
- }
- }
- if (sendBackLongPress) {
- KeyButtonView keyButtonView = (KeyButtonView) v;
- keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
- keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ if (activityManager.isInLockTaskMode()) {
+ activityManager.stopLockTaskModeOnCurrent();
+
+ // When exiting refresh disabled flags.
+ mNavigationBarView.setDisabledFlags(mDisabled1, true);
+ return true;
}
} catch (RemoteException e) {
Log.d(TAG, "Unable to reach activity manager", e);
}
+ return false;
}
// Recents
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 4ce0933..05e3fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -46,6 +46,7 @@
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.Host.Callback;
import com.android.systemui.qs.QSTile.ResourceIcon;
+import com.android.systemui.qs.QSTileBaseView;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -381,7 +382,7 @@
private static class DraggableTile extends QSTile<QSTile.State>
implements DropListener {
private String mSpec;
- private QSTileView mView;
+ private QSTileBaseView mView;
protected DraggableTile(QSTile.Host host, String tileSpec) {
super(host);
@@ -390,7 +391,7 @@
}
@Override
- public QSTileView createTileView(Context context) {
+ public QSTileBaseView createTileView(Context context) {
mView = super.createTileView(context);
return mView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 1cf7a70f..673a30b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -125,10 +125,6 @@
return mAudio;
}
- public ZenModeConfig getZenModeConfig() {
- return mNoMan.getZenModeConfig();
- }
-
public void dismiss() {
mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
}
@@ -348,7 +344,6 @@
updateRingerModeExternalW(mAudio.getRingerMode());
updateZenModeW();
updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
- updateZenModeConfigW();
mCallbacks.onStateChanged(mState);
}
@@ -401,13 +396,6 @@
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
- private boolean updateZenModeConfigW() {
- final ZenModeConfig zenModeConfig = getZenModeConfig();
- if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false;
- mState.zenModeConfig = zenModeConfig;
- return true;
- }
-
private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
mState.effectsSuppressor = effectsSuppressor;
@@ -748,9 +736,6 @@
if (ZEN_MODE_URI.equals(uri)) {
changed = updateZenModeW();
}
- if (ZEN_MODE_CONFIG_URI.equals(uri)) {
- changed = updateZenModeConfigW();
- }
if (changed) {
mCallbacks.onStateChanged(mState);
}
@@ -947,7 +932,6 @@
public int zenMode;
public ComponentName effectsSuppressor;
public String effectsSuppressorName;
- public ZenModeConfig zenModeConfig;
public int activeStream = NO_ACTIVE_STREAM;
public State copy() {
@@ -960,7 +944,6 @@
rt.zenMode = zenMode;
if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
rt.effectsSuppressorName = effectsSuppressorName;
- if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy();
rt.activeStream = activeStream;
return rt;
}
@@ -989,7 +972,6 @@
sep(sb, indent); sb.append("zenMode:").append(zenMode);
sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
- sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
sep(sb, indent); sb.append("activeStream:").append(activeStream);
if (indent > 0) sep(sb, indent);
return sb.append('}').toString();
@@ -1005,11 +987,6 @@
sb.append(',');
}
}
-
- public Condition getManualExitCondition() {
- return zenModeConfig != null && zenModeConfig.manualRule != null
- ? zenModeConfig.manualRule.condition : null;
- }
}
public interface Callbacks {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 08f0952e..a5ddc12 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -20,7 +20,10 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -94,11 +97,13 @@
static final boolean DEBUG_BATCH = localLOGV || false;
static final boolean DEBUG_VALIDATE = localLOGV || false;
static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
+ static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
static final boolean RECORD_ALARMS_IN_HISTORY = true;
+ static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
static final int ALARM_EVENT = 1;
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- static final Intent mBackgroundIntent
+ private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
@@ -109,6 +114,8 @@
final LocalLog mLog = new LocalLog(TAG);
+ AppOpsManager mAppOps;
+
final Object mLock = new Object();
long mNativeData;
@@ -123,7 +130,7 @@
ClockReceiver mClockReceiver;
InteractiveStateReceiver mInteractiveStateReceiver;
private UninstallReceiver mUninstallReceiver;
- final ResultReceiver mResultReceiver = new ResultReceiver();
+ final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
Random mRandom;
@@ -144,6 +151,16 @@
*/
final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
+ final static class IdleDispatchEntry {
+ int uid;
+ String pkg;
+ String tag;
+ String op;
+ long elapsedRealtime;
+ long argRealtime;
+ }
+ final ArrayList<IdleDispatchEntry> mAllowWhileIdleDispatches = new ArrayList();
+
/**
* Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
*/
@@ -174,13 +191,16 @@
private static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
= "allow_while_idle_whitelist_duration";
+ private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY;
- private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 15*60*1000;
+ private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
+ private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -197,6 +217,9 @@
public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
= DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+ // Direct alarm listener callback timeout
+ public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -253,6 +276,8 @@
ALLOW_WHILE_IDLE_WHITELIST_DURATION = mParser.getLong(
KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+ LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
+ DEFAULT_LISTENER_TIMEOUT);
updateAllowWhileIdleMinTimeLocked();
updateAllowWhileIdleWhitelistDurationLocked();
@@ -270,6 +295,10 @@
TimeUtils.formatDuration(MIN_INTERVAL, pw);
pw.println();
+ pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+ TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
+ pw.println();
+
pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
pw.println();
@@ -377,14 +406,21 @@
return newStart;
}
- boolean remove(final PendingIntent operation) {
+ boolean remove(final PendingIntent operation, final IAlarmListener listener) {
+ if (operation == null && listener == null) {
+ if (localLOGV) {
+ Slog.w(TAG, "requested remove() of null operation",
+ new RuntimeException("here"));
+ }
+ return false;
+ }
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (alarm.operation.equals(operation)) {
+ if (alarm.matches(operation, listener)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -411,13 +447,20 @@
}
boolean remove(final String packageName) {
+ if (packageName == null) {
+ if (localLOGV) {
+ Slog.w(TAG, "requested remove() of null packageName",
+ new RuntimeException("here"));
+ }
+ return false;
+ }
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (alarm.operation.getTargetPackage().equals(packageName)) {
+ if (alarm.matches(packageName)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -449,7 +492,7 @@
long newEnd = Long.MAX_VALUE;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+ if (UserHandle.getUserId(alarm.creatorUid) == userHandle) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -477,7 +520,7 @@
final int N = alarms.size();
for (int i = 0; i < N; i++) {
Alarm a = alarms.get(i);
- if (a.operation.getTargetPackage().equals(packageName)) {
+ if (a.matches(packageName)) {
return true;
}
}
@@ -554,7 +597,8 @@
Alarm a = alarms.get(i);
final int alarmPrio;
- if (Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
+ if (a.operation != null
+ && Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
alarmPrio = PRIO_TICK;
} else if (a.wakeup) {
alarmPrio = PRIO_WAKEUP;
@@ -563,10 +607,13 @@
}
PriorityClass packagePrio = a.priorityClass;
- if (packagePrio == null) packagePrio = mPriorities.get(a.operation.getCreatorPackage());
+ String alarmPackage = (a.operation != null)
+ ? a.operation.getCreatorPackage()
+ : a.packageName;
+ if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage);
if (packagePrio == null) {
packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence
- mPriorities.put(a.operation.getCreatorPackage(), packagePrio);
+ mPriorities.put(alarmPackage, packagePrio);
}
a.priorityClass = packagePrio;
@@ -700,6 +747,14 @@
}
void restorePendingWhileIdleAlarmsLocked() {
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = 0;
+ ent.pkg = "FINISH IDLE";
+ ent.elapsedRealtime = SystemClock.elapsedRealtime();
+ mAllowWhileIdleDispatches.add(ent);
+ }
+
// Bring pending alarms back into the main list.
if (mPendingWhileIdleAlarms.size() > 0) {
ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
@@ -725,20 +780,27 @@
}
}
- static final class InFlight extends Intent {
+ static final class InFlight {
final PendingIntent mPendingIntent;
+ final IBinder mListener;
final WorkSource mWorkSource;
+ final int mUid;
final String mTag;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
- int alarmType, String tag, long nowELAPSED) {
+ InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
+ WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
+ long nowELAPSED) {
mPendingIntent = pendingIntent;
+ mListener = listener != null ? listener.asBinder() : null;
mWorkSource = workSource;
+ mUid = uid;
mTag = tag;
- mBroadcastStats = service.getStatsLocked(pendingIntent);
+ mBroadcastStats = (pendingIntent != null)
+ ? service.getStatsLocked(pendingIntent)
+ : service.getStatsLocked(uid, alarmPkg);
FilterStats fs = mBroadcastStats.filterStats.get(mTag);
if (fs == null) {
fs = new FilterStats(mBroadcastStats, mTag);
@@ -834,6 +896,7 @@
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mConstants.start(getContext().getContentResolver());
+ mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
}
}
@@ -886,15 +949,20 @@
return;
}
synchronized (mLock) {
- removeLocked(operation);
+ removeLocked(operation, null);
}
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, int flags, WorkSource workSource,
- AlarmManager.AlarmClockInfo alarmClock, int callingUid) {
- if (operation == null) {
- Slog.w(TAG, "set/setRepeating ignored because there is no intent");
+ PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+ int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
+ int callingUid, String callingPackage) {
+ // must be *either* PendingIntent or AlarmReceiver, but not both
+ if ((operation == null && directReceiver == null)
+ || (operation != null && directReceiver != null)) {
+ Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
+ // NB: previous releases failed silently here, so we are continuing to do the same
+ // rather than throw an IllegalArgumentException.
return;
}
@@ -952,17 +1020,19 @@
+ " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, flags, true, workSource, alarmClock, callingUid);
+ interval, operation, directReceiver, listenerTag, flags, true, workSource,
+ alarmClock, callingUid, callingPackage);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, int flags,
- boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
- int uid) {
+ long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+ String listenerTag, int flags, boolean doValidate, WorkSource workSource,
+ AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, flags, alarmClock, uid);
- removeLocked(operation);
+ operation, directReceiver, listenerTag, workSource, flags, alarmClock,
+ callingUid, callingPackage);
+ removeLocked(operation, directReceiver);
setImplLocked(a, false, doValidate);
}
@@ -1006,6 +1076,19 @@
}
}
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ if ((a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = a.uid;
+ ent.pkg = a.operation.getCreatorPackage();
+ ent.tag = a.operation.getTag("");
+ ent.op = "SET";
+ ent.elapsedRealtime = SystemClock.elapsedRealtime();
+ ent.argRealtime = a.whenElapsed;
+ mAllowWhileIdleDispatches.add(ent);
+ }
+ }
+
int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
if (whichBatch < 0) {
@@ -1028,6 +1111,15 @@
boolean needRebatch = false;
if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ if (mPendingIdleUntil == null) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = 0;
+ ent.pkg = "START IDLE";
+ ent.elapsedRealtime = SystemClock.elapsedRealtime();
+ mAllowWhileIdleDispatches.add(ent);
+ }
+ }
mPendingIdleUntil = a;
mConstants.updateAllowWhileIdleMinTimeLocked();
needRebatch = true;
@@ -1068,10 +1160,31 @@
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
- PendingIntent operation, WorkSource workSource,
- AlarmManager.AlarmClockInfo alarmClock) {
+ public void set(String callingPackage,
+ int type, long triggerAtTime, long windowLength, long interval, int flags,
+ PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+ WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
final int callingUid = Binder.getCallingUid();
+
+ // make sure the caller is not lying about which package should be blamed for
+ // wakelock time spent in alarm delivery
+ mAppOps.checkPackage(callingUid, callingPackage);
+
+ // Repeating alarms must use PendingIntent, not direct listener
+ if (interval != 0) {
+ if (directReceiver != null) {
+ throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+ }
+ }
+
+ // direct-callback alarms must be wakeup alarms (otherwise they should just be
+ // posting work to a Handler)
+ if (directReceiver != null) {
+ if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) {
+ throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers");
+ }
+ }
+
if (workSource != null) {
getContext().enforcePermission(
android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -1108,8 +1221,8 @@
flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
}
- setImpl(type, triggerAtTime, windowLength, interval, operation,
- flags, workSource, alarmClock, callingUid);
+ setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
+ listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
}
@Override
@@ -1143,9 +1256,15 @@
}
@Override
- public void remove(PendingIntent operation) {
- removeImpl(operation);
+ public void remove(PendingIntent operation, IAlarmListener listener) {
+ if (operation == null && listener == null) {
+ Slog.w(TAG, "remove() with no intent or listener");
+ return;
+ }
+ synchronized (mLock) {
+ removeLocked(operation, listener);
+ }
}
@Override
@@ -1400,6 +1519,32 @@
}
}
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ pw.println();
+ pw.println(" Allow while idle dispatches:");
+ for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+ IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+ pw.print(" ");
+ TimeUtils.formatDuration(ent.elapsedRealtime, nowELAPSED, pw);
+ pw.print(": ");
+ UserHandle.formatUid(pw, ent.uid);
+ pw.print(":");
+ pw.println(ent.pkg);
+ if (ent.op != null) {
+ pw.print(" ");
+ pw.print(ent.op);
+ pw.print(" / ");
+ pw.print(ent.tag);
+ if (ent.argRealtime != 0) {
+ pw.print(" (");
+ TimeUtils.formatDuration(ent.argRealtime, nowELAPSED, pw);
+ pw.print(")");
+ }
+ pw.println();
+ }
+ }
+ }
+
if (WAKEUP_STATS) {
pw.println();
pw.println(" Recent Wakeup History:");
@@ -1630,17 +1775,17 @@
}
}
- private void removeLocked(PendingIntent operation) {
+ private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
boolean didRemove = false;
for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
Batch b = mAlarmBatches.get(i);
- didRemove |= b.remove(operation);
+ didRemove |= b.remove(operation, directReceiver);
if (b.size() == 0) {
mAlarmBatches.remove(i);
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
}
@@ -1651,11 +1796,11 @@
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
boolean restorePending = false;
- if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {
mPendingIdleUntil = null;
restorePending = true;
}
- if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) {
+ if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
mNextWakeFromIdle = null;
}
rebatchAllAlarmsLocked(true);
@@ -1676,7 +1821,8 @@
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ final Alarm a = mPendingWhileIdleAlarms.get(i);
+ if (a.matches(packageName)) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
}
@@ -1702,7 +1848,7 @@
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).creatorUid)
== userHandle) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
@@ -1758,7 +1904,8 @@
}
}
for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
- if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ final Alarm a = mPendingWhileIdleAlarms.get(i);
+ if (a.matches(packageName)) {
return true;
}
}
@@ -1862,6 +2009,16 @@
if (alarm.maxWhenElapsed < minTime) {
alarm.maxWhenElapsed = minTime;
}
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = alarm.uid;
+ ent.pkg = alarm.operation.getCreatorPackage();
+ ent.tag = alarm.operation.getTag("");
+ ent.op = "RESCHEDULE";
+ ent.elapsedRealtime = nowELAPSED;
+ ent.argRealtime = lastTime;
+ mAllowWhileIdleDispatches.add(ent);
+ }
setImplLocked(alarm, true, false);
continue;
}
@@ -1871,7 +2028,7 @@
triggerList.add(alarm);
if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
- alarm.tag);
+ alarm.statsTag);
}
if (mPendingIdleUntil == alarm) {
mPendingIdleUntil = null;
@@ -1895,8 +2052,8 @@
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, alarm.flags, true,
- alarm.workSource, alarm.alarmClock, alarm.uid);
+ alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+ alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
}
if (alarm.wakeup) {
@@ -1947,11 +2104,15 @@
public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
- public final String tag;
+ public final IAlarmListener listener;
+ public final String listenerTag;
+ public final String statsTag;
public final WorkSource workSource;
public final int flags;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int uid;
+ public final int creatorUid;
+ public final String packageName;
public int count;
public long when;
public long windowLength;
@@ -1961,8 +2122,9 @@
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws, int _flags,
- AlarmManager.AlarmClockInfo _info, int _uid) {
+ long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
+ WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
+ int _uid, String _pkgName) {
type = _type;
origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -1973,16 +2135,42 @@
maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
- tag = makeTag(_op, _type);
+ listener = _rec;
+ listenerTag = _listenerTag;
+ statsTag = makeTag(_op, _listenerTag, _type);
workSource = _ws;
flags = _flags;
alarmClock = _info;
uid = _uid;
+ packageName = _pkgName;
+
+ creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
}
- public static String makeTag(PendingIntent pi, int type) {
- return pi.getTag(type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
- ? "*walarm*:" : "*alarm*:");
+ public static String makeTag(PendingIntent pi, String tag, int type) {
+ final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
+ ? "*walarm*:" : "*alarm*:";
+ return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
+ }
+
+ public WakeupEvent makeWakeupEvent(long nowRTC) {
+ return new WakeupEvent(nowRTC, creatorUid,
+ (operation != null)
+ ? operation.getIntent().getAction()
+ : ("<listener>:" + listenerTag));
+ }
+
+ // Returns true if either matches
+ public boolean matches(PendingIntent pi, IAlarmListener rec) {
+ return (operation != null)
+ ? operation.equals(pi)
+ : listener.asBinder().equals(rec.asBinder());
+ }
+
+ public boolean matches(String packageName) {
+ return (operation != null)
+ ? packageName.equals(operation.getTargetPackage())
+ : packageName.equals(this.packageName);
}
@Override
@@ -1995,7 +2183,11 @@
sb.append(" when ");
sb.append(when);
sb.append(" ");
- sb.append(operation.getTargetPackage());
+ if (operation != null) {
+ sb.append(operation.getTargetPackage());
+ } else {
+ sb.append(listener.asBinder().toString());
+ }
sb.append('}');
return sb.toString();
}
@@ -2003,14 +2195,15 @@
public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
SimpleDateFormat sdf) {
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
- pw.print(prefix); pw.print("tag="); pw.println(tag);
+ pw.print(prefix); pw.print("tag="); pw.println(statsTag);
pw.print(prefix); pw.print("type="); pw.print(type);
pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
nowELAPSED, pw);
+ pw.print(" when=");
if (isRtc) {
- pw.print(" when="); pw.print(sdf.format(new Date(when)));
+ pw.print(sdf.format(new Date(when)));
} else {
- pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw);
+ TimeUtils.formatDuration(when, nowELAPSED, pw);
}
pw.println();
pw.print(prefix); pw.print("window="); TimeUtils.formatDuration(windowLength, pw);
@@ -2024,6 +2217,9 @@
pw.print(prefix); pw.print(" showIntent="); pw.println(alarmClock.getShowIntent());
}
pw.print(prefix); pw.print("operation="); pw.println(operation);
+ if (listener != null) {
+ pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
+ }
}
}
@@ -2038,10 +2234,7 @@
final int numAlarms = b.alarms.size();
for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) {
Alarm a = b.alarms.get(nextAlarm);
- WakeupEvent e = new WakeupEvent(nowRTC,
- a.operation.getCreatorUid(),
- a.operation.getIntent().getAction());
- mRecentWakeups.add(e);
+ mRecentWakeups.add(a.makeWakeupEvent(nowRTC));
}
}
}
@@ -2104,71 +2297,14 @@
if (alarm.workSource != null && alarm.workSource.size() > 0) {
for (int wi=0; wi<alarm.workSource.size(); wi++) {
ActivityManagerNative.noteAlarmStart(
- alarm.operation, alarm.workSource.get(wi), alarm.tag);
+ alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
}
} else {
ActivityManagerNative.noteAlarmStart(
- alarm.operation, -1, alarm.tag);
+ alarm.operation, alarm.uid, alarm.statsTag);
}
}
- alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mResultReceiver, mHandler, null, allowWhileIdle ? mIdleOptions : null);
-
- // we have an active broadcast so stay awake.
- if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource,
- alarm.type, alarm.tag, true);
- mWakeLock.acquire();
- }
- final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED);
- mInFlight.add(inflight);
- mBroadcastRefCount++;
-
- if (allowWhileIdle) {
- // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
- mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
- }
-
- final BroadcastStats bs = inflight.mBroadcastStats;
- bs.count++;
- if (bs.nesting == 0) {
- bs.nesting = 1;
- bs.startTime = nowELAPSED;
- } else {
- bs.nesting++;
- }
- final FilterStats fs = inflight.mFilterStats;
- fs.count++;
- if (fs.nesting == 0) {
- fs.nesting = 1;
- fs.startTime = nowELAPSED;
- } else {
- fs.nesting++;
- }
- if (alarm.type == ELAPSED_REALTIME_WAKEUP
- || alarm.type == RTC_WAKEUP) {
- bs.numWakeup++;
- fs.numWakeup++;
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- ActivityManagerNative.noteWakeupAlarm(
- alarm.operation, alarm.workSource.get(wi),
- alarm.workSource.getName(wi), alarm.tag);
- }
- } else {
- ActivityManagerNative.noteWakeupAlarm(
- alarm.operation, -1, null, alarm.tag);
- }
- }
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss the hoser.
- removeImpl(alarm.operation);
- }
+ mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
@@ -2298,9 +2434,10 @@
* Attribute blame for a WakeLock.
* @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
+ * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
*/
void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
- boolean first) {
+ int knownUid, boolean first) {
try {
final boolean unimportant = pi == mTimeTickSender;
mWakeLock.setUnimportantForLogging(unimportant);
@@ -2315,8 +2452,9 @@
return;
}
- final int uid = ActivityManagerNative.getDefault()
- .getUidForIntentSender(pi.getTarget());
+ final int uid = (knownUid >= 0)
+ ? knownUid
+ : ActivityManagerNative.getDefault().getUidForIntentSender(pi.getTarget());
if (uid >= 0) {
mWakeLock.setWorkSource(new WorkSource(uid));
return;
@@ -2333,35 +2471,49 @@
public static final int MINUTE_CHANGE_EVENT = 2;
public static final int DATE_CHANGE_EVENT = 3;
public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
+ public static final int LISTENER_TIMEOUT = 5;
public AlarmHandler() {
}
public void handleMessage(Message msg) {
- if (msg.what == ALARM_EVENT) {
- ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
- synchronized (mLock) {
- final long nowRTC = System.currentTimeMillis();
- final long nowELAPSED = SystemClock.elapsedRealtime();
- triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
- updateNextAlarmClockLocked();
- }
+ switch (msg.what) {
+ case ALARM_EVENT: {
+ ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+ synchronized (mLock) {
+ final long nowRTC = System.currentTimeMillis();
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+ updateNextAlarmClockLocked();
+ }
- // now trigger the alarms without the lock held
- for (int i=0; i<triggerList.size(); i++) {
- Alarm alarm = triggerList.get(i);
- try {
- alarm.operation.send();
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss the hoser.
- removeImpl(alarm.operation);
+ // now trigger the alarms without the lock held
+ for (int i=0; i<triggerList.size(); i++) {
+ Alarm alarm = triggerList.get(i);
+ try {
+ alarm.operation.send();
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss the hoser.
+ removeImpl(alarm.operation);
+ }
}
}
+ break;
}
- } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
- sendNextAlarmClockChanged();
+
+ case SEND_NEXT_ALARM_CLOCK_CHANGED:
+ sendNextAlarmClockChanged();
+ break;
+
+ case LISTENER_TIMEOUT:
+ mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
+ break;
+
+ default:
+ // nope, just ignore it
+ break;
}
}
}
@@ -2403,8 +2555,8 @@
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null,
- Process.myUid());
+ 0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
+ null, Process.myUid(), "android");
}
public void scheduleDateChangedEvent() {
@@ -2417,8 +2569,9 @@
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
- AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid());
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
+ AlarmManager.FLAG_STANDALONE, workSource, null,
+ Process.myUid(), "android");
}
}
@@ -2516,80 +2669,260 @@
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
int uid = pi.getCreatorUid();
+ return getStatsLocked(uid, pkg);
+ }
+
+ private final BroadcastStats getStatsLocked(int uid, String pkgName) {
ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.get(uid);
if (uidStats == null) {
uidStats = new ArrayMap<String, BroadcastStats>();
mBroadcastStats.put(uid, uidStats);
}
- BroadcastStats bs = uidStats.get(pkg);
+ BroadcastStats bs = uidStats.get(pkgName);
if (bs == null) {
- bs = new BroadcastStats(uid, pkg);
- uidStats.put(pkg, bs);
+ bs = new BroadcastStats(uid, pkgName);
+ uidStats.put(pkgName, bs);
}
return bs;
}
- class ResultReceiver implements PendingIntent.OnFinished {
+ class DeliveryTracker extends IAlarmCompleteListener.Stub implements PendingIntent.OnFinished {
+ private InFlight removeLocked(PendingIntent pi, Intent intent) {
+ for (int i = 0; i < mInFlight.size(); i++) {
+ if (mInFlight.get(i).mPendingIntent == pi) {
+ return mInFlight.remove(i);
+ }
+ }
+ mLog.w("No in-flight alarm for " + pi + " " + intent);
+ return null;
+ }
+
+ private InFlight removeLocked(IBinder listener) {
+ for (int i = 0; i < mInFlight.size(); i++) {
+ if (mInFlight.get(i).mListener == listener) {
+ return mInFlight.remove(i);
+ }
+ }
+ mLog.w("No in-flight alarm for listener " + listener);
+ return null;
+ }
+
+ private void updateStatsLocked(InFlight inflight) {
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ BroadcastStats bs = inflight.mBroadcastStats;
+ bs.nesting--;
+ if (bs.nesting <= 0) {
+ bs.nesting = 0;
+ bs.aggregateTime += nowELAPSED - bs.startTime;
+ }
+ FilterStats fs = inflight.mFilterStats;
+ fs.nesting--;
+ if (fs.nesting <= 0) {
+ fs.nesting = 0;
+ fs.aggregateTime += nowELAPSED - fs.startTime;
+ }
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
+ for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
+ ActivityManagerNative.noteAlarmFinish(
+ inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
+ }
+ } else {
+ ActivityManagerNative.noteAlarmFinish(
+ inflight.mPendingIntent, inflight.mUid, inflight.mTag);
+ }
+ }
+ }
+
+ private void updateTrackingLocked(InFlight inflight) {
+ if (inflight != null) {
+ updateStatsLocked(inflight);
+ }
+ mBroadcastRefCount--;
+ if (mBroadcastRefCount == 0) {
+ mWakeLock.release();
+ if (mInFlight.size() > 0) {
+ mLog.w("Finished all dispatches with " + mInFlight.size()
+ + " remaining inflights");
+ for (int i=0; i<mInFlight.size(); i++) {
+ mLog.w(" Remaining #" + i + ": " + mInFlight.get(i));
+ }
+ mInFlight.clear();
+ }
+ } else {
+ // the next of our alarms is now in flight. reattribute the wakelock.
+ if (mInFlight.size() > 0) {
+ InFlight inFlight = mInFlight.get(0);
+ setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
+ inFlight.mAlarmType, inFlight.mTag, -1, false);
+ } else {
+ // should never happen
+ mLog.w("Alarm wakelock still held but sent queue empty");
+ mWakeLock.setWorkSource(null);
+ }
+ }
+ }
+
+ /**
+ * Callback that arrives when a direct-call alarm reports that delivery has finished
+ */
+ @Override
+ public void alarmComplete(IBinder who) {
+ if (who == null) {
+ Slog.w(TAG, "Invalid alarmComplete: uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid());
+ return;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mHandler.removeMessages(AlarmHandler.LISTENER_TIMEOUT, who);
+ InFlight inflight = removeLocked(who);
+ if (inflight != null) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "alarmComplete() from " + who);
+ }
+ updateTrackingLocked(inflight);
+ } else {
+ // Delivery timed out, and the timeout handling already took care of
+ // updating our tracking here, so we needn't do anything further.
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Late alarmComplete() from " + who);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Callback that arrives when a PendingIntent alarm has finished delivery
+ */
+ @Override
public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
String resultData, Bundle resultExtras) {
synchronized (mLock) {
- InFlight inflight = null;
- for (int i=0; i<mInFlight.size(); i++) {
- if (mInFlight.get(i).mPendingIntent == pi) {
- inflight = mInFlight.remove(i);
- break;
- }
- }
+ updateTrackingLocked(removeLocked(pi, intent));
+ }
+ }
+
+ /**
+ * Timeout of a direct-call alarm delivery
+ */
+ public void alarmTimedOut(IBinder who) {
+ synchronized (mLock) {
+ InFlight inflight = removeLocked(who);
if (inflight != null) {
- final long nowELAPSED = SystemClock.elapsedRealtime();
- BroadcastStats bs = inflight.mBroadcastStats;
- bs.nesting--;
- if (bs.nesting <= 0) {
- bs.nesting = 0;
- bs.aggregateTime += nowELAPSED - bs.startTime;
+ // TODO: implement ANR policy for the target
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm listener " + who + " timed out in delivery");
}
- FilterStats fs = inflight.mFilterStats;
- fs.nesting--;
- if (fs.nesting <= 0) {
- fs.nesting = 0;
- fs.aggregateTime += nowELAPSED - fs.startTime;
- }
- if (RECORD_ALARMS_IN_HISTORY) {
- if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
- for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
- ActivityManagerNative.noteAlarmFinish(
- pi, inflight.mWorkSource.get(wi), inflight.mTag);
- }
- } else {
- ActivityManagerNative.noteAlarmFinish(
- pi, -1, inflight.mTag);
- }
- }
+ updateTrackingLocked(inflight);
} else {
- mLog.w("No in-flight alarm for " + pi + " " + intent);
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Spurious timeout of listener " + who);
+ }
}
- mBroadcastRefCount--;
- if (mBroadcastRefCount == 0) {
- mWakeLock.release();
- if (mInFlight.size() > 0) {
- mLog.w("Finished all broadcasts with " + mInFlight.size()
- + " remaining inflights");
- for (int i=0; i<mInFlight.size(); i++) {
- mLog.w(" Remaining #" + i + ": " + mInFlight.get(i));
- }
- mInFlight.clear();
+ }
+ }
+
+ /**
+ * Deliver an alarm and set up the post-delivery handling appropriately
+ */
+ public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+ if (alarm.operation != null) {
+ // PendingIntent alarm
+ try {
+ alarm.operation.send(getContext(), 0,
+ mBackgroundIntent.putExtra(
+ Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null,
+ allowWhileIdle ? mIdleOptions : null);
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss it
+ removeImpl(alarm.operation);
+ }
+ }
+ } else {
+ // Direct listener callback alarm
+ try {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ + " listener=" + alarm.listener.asBinder());
+ }
+ alarm.listener.doAlarm(this);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+ alarm.listener.asBinder()),
+ mConstants.LISTENER_TIMEOUT);
+ } catch (Exception e) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm undeliverable to listener "
+ + alarm.listener.asBinder(), e);
+ }
+ }
+ }
+
+ // The alarm is now in flight; now arrange wakelock and stats tracking
+ if (mBroadcastRefCount == 0) {
+ setWakelockWorkSource(alarm.operation, alarm.workSource,
+ alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
+ true);
+ mWakeLock.acquire();
+ }
+ final InFlight inflight = new InFlight(AlarmManagerService.this,
+ alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
+ alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+ mInFlight.add(inflight);
+ mBroadcastRefCount++;
+
+ if (allowWhileIdle) {
+ // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
+ mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = alarm.uid;
+ ent.pkg = alarm.packageName;
+ ent.tag = alarm.statsTag;
+ ent.op = "DELIVER";
+ ent.elapsedRealtime = nowELAPSED;
+ mAllowWhileIdleDispatches.add(ent);
+ }
+ }
+
+ final BroadcastStats bs = inflight.mBroadcastStats;
+ bs.count++;
+ if (bs.nesting == 0) {
+ bs.nesting = 1;
+ bs.startTime = nowELAPSED;
+ } else {
+ bs.nesting++;
+ }
+ final FilterStats fs = inflight.mFilterStats;
+ fs.count++;
+ if (fs.nesting == 0) {
+ fs.nesting = 1;
+ fs.startTime = nowELAPSED;
+ } else {
+ fs.nesting++;
+ }
+ if (alarm.type == ELAPSED_REALTIME_WAKEUP
+ || alarm.type == RTC_WAKEUP) {
+ bs.numWakeup++;
+ fs.numWakeup++;
+ if (alarm.workSource != null && alarm.workSource.size() > 0) {
+ for (int wi=0; wi<alarm.workSource.size(); wi++) {
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, alarm.workSource.get(wi),
+ alarm.workSource.getName(wi), alarm.statsTag);
}
} else {
- // the next of our alarms is now in flight. reattribute the wakelock.
- if (mInFlight.size() > 0) {
- InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
- inFlight.mAlarmType, inFlight.mTag, false);
- } else {
- // should never happen
- mLog.w("Alarm wakelock still held but sent queue empty");
- mWakeLock.setWorkSource(null);
- }
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, -1, alarm.packageName, alarm.statsTag);
}
}
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2eeaec9..61fe62f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -19,6 +19,8 @@
import android.database.ContentObserver;
import android.os.BatteryStats;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
import com.android.internal.app.IBatteryStats;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.Light;
@@ -96,7 +98,6 @@
// discharge stats before the device dies.
private int mCriticalBatteryLevel;
- private static final int DUMP_MAX_LENGTH = 24 * 1024;
private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" };
private static final String DUMPSYS_DATA_PATH = "/data/system/";
@@ -106,6 +107,7 @@
private final Context mContext;
private final IBatteryStats mBatteryStats;
+ BinderService mBinderService;
private final Handler mHandler;
private final Object mLock = new Object();
@@ -162,7 +164,18 @@
// watch for invalid charger messages if the invalid_charger switch exists
if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
- mInvalidChargerObserver.startObserving(
+ UEventObserver invalidChargerObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEvent event) {
+ final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
+ synchronized (mLock) {
+ if (mInvalidCharger != invalidCharger) {
+ mInvalidCharger = invalidCharger;
+ }
+ }
+ }
+ };
+ invalidChargerObserver.startObserving(
"DEVPATH=/devices/virtual/switch/invalid_charger");
}
}
@@ -178,7 +191,8 @@
// Should never happen.
}
- publishBinderService("battery", new BinderService());
+ mBinderService = new BinderService();
+ publishBinderService("battery", mBinderService);
publishLocalService(BatteryManagerInternal.class, new LocalService());
}
@@ -593,7 +607,6 @@
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
durationThresholdString + " or " + dischargeThresholdString);
- return;
}
}
}
@@ -616,7 +629,133 @@
}
}
- private void dumpInternal(PrintWriter pw, String[] args) {
+ class Shell extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ dumpHelp(pw);
+ }
+ }
+
+ static void dumpHelp(PrintWriter pw) {
+ pw.println("Battery service (battery) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" set [ac|usb|wireless|status|level|invalid] <value>");
+ pw.println(" Force a battery property value, freezing battery state.");
+ pw.println(" unplug");
+ pw.println(" Force battery unplugged, freezing battery state.");
+ pw.println(" reset");
+ pw.println(" Unfreeze battery state, returning to current hardware values.");
+ }
+
+ int onShellCommand(Shell shell, String cmd) {
+ if (cmd == null) {
+ return shell.handleDefaultCommands(cmd);
+ }
+ PrintWriter pw = shell.getOutPrintWriter();
+ switch (cmd) {
+ case "unplug": {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ if (!mUpdatesStopped) {
+ mLastBatteryProps.set(mBatteryProps);
+ }
+ mBatteryProps.chargerAcOnline = false;
+ mBatteryProps.chargerUsbOnline = false;
+ mBatteryProps.chargerWirelessOnline = false;
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mUpdatesStopped = true;
+ processValuesLocked(false);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } break;
+ case "set": {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ final String key = shell.getNextArg();
+ if (key == null) {
+ pw.println("No property specified");
+ return -1;
+
+ }
+ final String value = shell.getNextArg();
+ if (value == null) {
+ pw.println("No value specified");
+ return -1;
+
+ }
+ try {
+ if (!mUpdatesStopped) {
+ mLastBatteryProps.set(mBatteryProps);
+ }
+ boolean update = true;
+ switch (key) {
+ case "ac":
+ mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+ break;
+ case "usb":
+ mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+ break;
+ case "wireless":
+ mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ break;
+ case "status":
+ mBatteryProps.batteryStatus = Integer.parseInt(value);
+ break;
+ case "level":
+ mBatteryProps.batteryLevel = Integer.parseInt(value);
+ break;
+ case "invalid":
+ mInvalidCharger = Integer.parseInt(value);
+ break;
+ default:
+ pw.println("Unknown set option: " + key);
+ update = false;
+ break;
+ }
+ if (update) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mUpdatesStopped = true;
+ processValuesLocked(false);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } catch (NumberFormatException ex) {
+ pw.println("Bad value: " + value);
+ return -1;
+ }
+ } break;
+ case "reset": {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mUpdatesStopped) {
+ mUpdatesStopped = false;
+ mBatteryProps.set(mLastBatteryProps);
+ processValuesLocked(false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } break;
+ default:
+ return shell.handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
if (args == null || args.length == 0 || "-a".equals(args[0])) {
pw.println("Current Battery Service state:");
@@ -635,91 +774,13 @@
pw.println(" voltage: " + mBatteryProps.batteryVoltage);
pw.println(" temperature: " + mBatteryProps.batteryTemperature);
pw.println(" technology: " + mBatteryProps.batteryTechnology);
-
- } else if ("unplug".equals(args[0])) {
- if (!mUpdatesStopped) {
- mLastBatteryProps.set(mBatteryProps);
- }
- mBatteryProps.chargerAcOnline = false;
- mBatteryProps.chargerUsbOnline = false;
- mBatteryProps.chargerWirelessOnline = false;
- long ident = Binder.clearCallingIdentity();
- try {
- mUpdatesStopped = true;
- processValuesLocked(false);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- } else if (args.length == 3 && "set".equals(args[0])) {
- String key = args[1];
- String value = args[2];
- try {
- if (!mUpdatesStopped) {
- mLastBatteryProps.set(mBatteryProps);
- }
- boolean update = true;
- if ("ac".equals(key)) {
- mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
- } else if ("usb".equals(key)) {
- mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
- } else if ("wireless".equals(key)) {
- mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
- } else if ("status".equals(key)) {
- mBatteryProps.batteryStatus = Integer.parseInt(value);
- } else if ("level".equals(key)) {
- mBatteryProps.batteryLevel = Integer.parseInt(value);
- } else if ("invalid".equals(key)) {
- mInvalidCharger = Integer.parseInt(value);
- } else {
- pw.println("Unknown set option: " + key);
- update = false;
- }
- if (update) {
- long ident = Binder.clearCallingIdentity();
- try {
- mUpdatesStopped = true;
- processValuesLocked(false);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } catch (NumberFormatException ex) {
- pw.println("Bad value: " + value);
- }
-
- } else if (args.length == 1 && "reset".equals(args[0])) {
- long ident = Binder.clearCallingIdentity();
- try {
- if (mUpdatesStopped) {
- mUpdatesStopped = false;
- mBatteryProps.set(mLastBatteryProps);
- processValuesLocked(false);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
} else {
- pw.println("Dump current battery state, or:");
- pw.println(" set [ac|usb|wireless|status|level|invalid] <value>");
- pw.println(" unplug");
- pw.println(" reset");
+ Shell shell = new Shell();
+ shell.exec(mBinderService, null, fd, null, args, new ResultReceiver(null));
}
}
}
- private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
- synchronized (mLock) {
- if (mInvalidCharger != invalidCharger) {
- mInvalidCharger = invalidCharger;
- }
- }
- }
- };
-
private final class Led {
private final Light mBatteryLight;
@@ -776,8 +837,7 @@
}
private final class BatteryListener extends IBatteryPropertiesListener.Stub {
- @Override
- public void batteryPropertiesChanged(BatteryProperties props) {
+ @Override public void batteryPropertiesChanged(BatteryProperties props) {
final long identity = Binder.clearCallingIdentity();
try {
BatteryService.this.update(props);
@@ -788,8 +848,7 @@
}
private final class BinderService extends Binder {
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -799,7 +858,12 @@
return;
}
- dumpInternal(pw, args);
+ dumpInternal(fd, pw, args);
+ }
+
+ @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, resultReceiver);
}
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 496e4df..927b995 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1500,7 +1500,7 @@
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, "step");
- cancelAlarmLocked();
+ scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
cancelLocatingLocked();
mAnyMotionDetector.checkForAnyMotion();
mNotMoving = false;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 6b34612..1ff13b2 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3824,7 +3824,6 @@
boolean isPermitted =
isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
- Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted));
return getTypesForCaller(callingUid, userId, isPermitted);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9834757..5aab804 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -69,12 +69,14 @@
import android.os.BatteryStats;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.ResultReceiver;
import android.os.Trace;
import android.os.TransactionTooLargeException;
import android.os.WorkSource;
import android.os.storage.IMountService;
import android.os.storage.MountServiceInternal;
import android.os.storage.StorageManager;
+import android.provider.Settings.Global;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionSession;
import android.util.ArrayMap;
@@ -1189,6 +1191,7 @@
String mOrigDebugApp = null;
boolean mOrigWaitForDebugger = false;
boolean mAlwaysFinishActivities = false;
+ boolean mForceResizableActivites;
IActivityController mController = null;
String mProfileApp = null;
ProcessRecord mProfileProc = null;
@@ -3842,8 +3845,8 @@
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
- resultWho, requestCode, startFlags, profilerInfo, options,
- UserHandle.getCallingUserId());
+ resultWho, requestCode, startFlags, profilerInfo, options,
+ UserHandle.getCallingUserId());
}
@Override
@@ -4150,7 +4153,12 @@
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- return startActivityFromRecentsInner(taskId, launchStackId, options);
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return startActivityFromRecentsInner(taskId, launchStackId, options);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
final int startActivityFromRecentsInner(int taskId, int launchStackId, Bundle options) {
@@ -4172,8 +4180,12 @@
}
if (launchStackId != INVALID_STACK_ID && task.stack.mStackId != launchStackId) {
- mStackSupervisor.moveTaskToStackUncheckedLocked(
- task, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+ if (launchStackId == DOCKED_STACK_ID && options != null) {
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ mWindowManager.setDockedStackCreateMode(activityOptions.getDockCreateMode());
+ }
+ mStackSupervisor.moveTaskToStackLocked(
+ taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
}
if (task.getRootActivity() != null) {
@@ -6515,7 +6527,7 @@
@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations) {
+ int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
+ horizontalSizeConfiguration + " " + verticalSizeConfigurations);
synchronized (this) {
@@ -6525,7 +6537,7 @@
+ "found for: " + token);
}
record.setSizeConfigurations(horizontalSizeConfiguration,
- verticalSizeConfigurations);
+ verticalSizeConfigurations, smallestSizeConfigurations);
}
}
@@ -9027,7 +9039,8 @@
}
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r
+ " to stackId=" + stackId);
- mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS);
+ mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS,
+ "moveActivityToStack");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9047,7 +9060,8 @@
try {
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ " to stackId=" + stackId + " toTop=" + toTop);
- mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS);
+ mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS,
+ "moveTaskToStack");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9076,7 +9090,7 @@
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateMode(createMode);
mStackSupervisor.moveTaskToStackLocked(
- taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS);
+ taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11350,7 +11364,7 @@
}
public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11359,7 +11373,12 @@
if (mBatteryStatsService.isOnBattery()) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
BatteryStatsImpl.Uid.Pkg pkg =
stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
sourcePkg != null ? sourcePkg : rec.key.packageName);
@@ -11369,7 +11388,7 @@
}
public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11377,13 +11396,18 @@
synchronized (stats) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
}
}
public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11391,7 +11415,12 @@
synchronized (stats) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
}
}
@@ -11638,14 +11667,17 @@
private void retrieveSettings() {
final ContentResolver resolver = mContext.getContentResolver();
- String debugApp = Settings.Global.getString(
- resolver, Settings.Global.DEBUG_APP);
+ String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP);
boolean waitForDebugger = Settings.Global.getInt(
resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
boolean alwaysFinishActivities = Settings.Global.getInt(
resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
boolean forceRtl = Settings.Global.getInt(
resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
+ int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
+ boolean forceResizable = Settings.Global.getInt(
+ resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+ defaultForceResizable) != 0;
// Transfer any global setting for forcing RTL layout, into a System Property
SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
@@ -11660,6 +11692,7 @@
mDebugApp = mOrigDebugApp = debugApp;
mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
mAlwaysFinishActivities = alwaysFinishActivities;
+ mForceResizableActivites = forceResizable;
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, true);
@@ -12089,7 +12122,8 @@
AppsQueryHelper queryHelper = new AppsQueryHelper(mContext);
Set<String> enableApps = new HashSet<>();
enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
- | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM,
+ | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
+ | AppsQueryHelper.GET_DEFAULT_IMES,
/* systemAppsOnly */ true, UserHandle.SYSTEM));
ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
enableApps.addAll(wlApps);
@@ -13065,6 +13099,13 @@
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new ActivityManagerShellCommand(this, false)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (checkCallingPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -13101,34 +13142,7 @@
}
dumpClient = true;
} else if ("-h".equals(opt)) {
- pw.println("Activity manager dump options:");
- pw.println(" [-a] [-c] [-p package] [-h] [cmd] ...");
- pw.println(" cmd may be one of:");
- pw.println(" a[ctivities]: activity stack state");
- pw.println(" r[recents]: recent activities state");
- pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state");
- pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state");
- pw.println(" p[rocesses] [PACKAGE_NAME]: process state");
- pw.println(" o[om]: out of memory management");
- pw.println(" perm[issions]: URI permission grant state");
- pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
- pw.println(" provider [COMP_SPEC]: provider client-side state");
- pw.println(" s[ervices] [COMP_SPEC ...]: service state");
- pw.println(" as[sociations]: tracked app associations");
- pw.println(" service [COMP_SPEC]: service client-side state");
- pw.println(" package [PACKAGE_NAME]: all state related to given package");
- pw.println(" all: dump all activities");
- pw.println(" top: dump the top activity");
- pw.println(" write: write all pending state to storage");
- pw.println(" track-associations: enable association tracking");
- pw.println(" untrack-associations: disable and clear association tracking");
- pw.println(" cmd may also be a COMP_SPEC to dump activities.");
- pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),");
- pw.println(" a partial substring in a component name, a");
- pw.println(" hex object identifier.");
- pw.println(" -a: include all available server state.");
- pw.println(" -c: include client state.");
- pw.println(" -p: limit output to given package.");
+ ActivityManagerShellCommand.dumpHelp(pw, true);
return;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
@@ -13265,36 +13279,15 @@
synchronized (this) {
mServices.dumpServicesLocked(fd, pw, args, opti, true, dumpClient, dumpPackage);
}
- } else if ("write".equals(cmd)) {
- mTaskPersister.flush();
- pw.println("All tasks persisted.");
- return;
- } else if ("track-associations".equals(cmd)) {
- synchronized (this) {
- if (!mTrackingAssociations) {
- mTrackingAssociations = true;
- pw.println("Association tracking started.");
- } else {
- pw.println("Association tracking already enabled.");
- }
- }
- return;
- } else if ("untrack-associations".equals(cmd)) {
- synchronized (this) {
- if (mTrackingAssociations) {
- mTrackingAssociations = false;
- mAssociations.clear();
- pw.println("Association tracking stopped.");
- } else {
- pw.println("Association tracking not running.");
- }
- }
- return;
} else {
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
- pw.println("Bad activity command, or no activities match: " + cmd);
- pw.println("Use -h for help.");
+ ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
+ int res = shell.exec(this, null, fd, null, args, new ResultReceiver(null));
+ if (res < 0) {
+ pw.println("Bad activity command, or no activities match: " + cmd);
+ pw.println("Use -h for help.");
+ }
}
}
if (!more) {
@@ -13545,17 +13538,34 @@
}
if (mActiveUids.size() > 0) {
- if (needSep) {
- pw.println();
+ boolean printed = false;
+ int whichAppId = -1;
+ if (dumpPackage != null) {
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+ dumpPackage, 0);
+ whichAppId = UserHandle.getAppId(info.uid);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
}
- pw.println(" UID states:");
for (int i=0; i<mActiveUids.size(); i++) {
UidRecord uidRec = mActiveUids.valueAt(i);
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ continue;
+ }
+ if (!printed) {
+ printed = true;
+ if (needSep) {
+ pw.println();
+ }
+ pw.println(" UID states:");
+ needSep = true;
+ printedAnything = true;
+ }
pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
pw.print(": "); pw.println(uidRec);
}
- needSep = true;
- printedAnything = true;
}
if (mLruProcesses.size() > 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
new file mode 100644
index 0000000..d1e7e85
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.IActivityManager;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+class ActivityManagerShellCommand extends ShellCommand {
+ // IPC interface to activity manager -- don't need to do additional security checks.
+ final IActivityManager mInterface;
+
+ // Internal service impl -- must perform security checks before touching.
+ final ActivityManagerService mInternal;
+
+ final boolean mDumping;
+
+ ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) {
+ mInterface = service;
+ mInternal = service;
+ mDumping = dumping;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "force-stop":
+ return runForceStop(pw);
+ case "kill":
+ return runKill(pw);
+ case "kill-all":
+ return runKillAll(pw);
+ case "write":
+ return runWrite(pw);
+ case "track-associations":
+ return runTrackAssociations(pw);
+ case "untrack-associations":
+ return runUntrackAssociations(pw);
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ int runForceStop(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = parseUserArg(getNextArgRequired());
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInterface.forceStopPackage(getNextArgRequired(), userId);
+ return 0;
+ }
+
+ int runKill(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = parseUserArg(getNextArgRequired());
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInterface.killBackgroundProcesses(getNextArgRequired(), userId);
+ return 0;
+ }
+
+ int runKillAll(PrintWriter pw) throws RemoteException {
+ mInterface.killAllBackgroundProcesses();
+ return 0;
+ }
+
+ int runWrite(PrintWriter pw) {
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "registerUidObserver()");
+ mInternal.mTaskPersister.flush();
+ pw.println("All tasks persisted.");
+ return 0;
+ }
+
+ int runTrackAssociations(PrintWriter pw) {
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "registerUidObserver()");
+ synchronized (mInternal) {
+ if (!mInternal.mTrackingAssociations) {
+ mInternal.mTrackingAssociations = true;
+ pw.println("Association tracking started.");
+ } else {
+ pw.println("Association tracking already enabled.");
+ }
+ }
+ return 0;
+ }
+
+ int runUntrackAssociations(PrintWriter pw) {
+ mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "registerUidObserver()");
+ synchronized (mInternal) {
+ if (mInternal.mTrackingAssociations) {
+ mInternal.mTrackingAssociations = false;
+ mInternal.mAssociations.clear();
+ pw.println("Association tracking stopped.");
+ } else {
+ pw.println("Association tracking not running.");
+ }
+ }
+ return 0;
+ }
+
+ int parseUserArg(String arg) {
+ int userId;
+ if ("all".equals(arg)) {
+ userId = UserHandle.USER_ALL;
+ } else if ("current".equals(arg) || "cur".equals(arg)) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ try {
+ userId = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Bad user number: " + arg);
+ }
+ }
+ return userId;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ dumpHelp(pw, mDumping);
+ }
+
+ static void dumpHelp(PrintWriter pw, boolean dumping) {
+ if (dumping) {
+ pw.println("Activity manager dump options:");
+ pw.println(" [-a] [-c] [-p PACKAGE] [-h] [WHAT] ...");
+ pw.println(" WHAT may be one of:");
+ pw.println(" a[ctivities]: activity stack state");
+ pw.println(" r[recents]: recent activities state");
+ pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state");
+ pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state");
+ pw.println(" p[rocesses] [PACKAGE_NAME]: process state");
+ pw.println(" o[om]: out of memory management");
+ pw.println(" perm[issions]: URI permission grant state");
+ pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
+ pw.println(" provider [COMP_SPEC]: provider client-side state");
+ pw.println(" s[ervices] [COMP_SPEC ...]: service state");
+ pw.println(" as[sociations]: tracked app associations");
+ pw.println(" service [COMP_SPEC]: service client-side state");
+ pw.println(" package [PACKAGE_NAME]: all state related to given package");
+ pw.println(" all: dump all activities");
+ pw.println(" top: dump the top activity");
+ pw.println(" WHAT may also be a COMP_SPEC to dump activities.");
+ pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),");
+ pw.println(" a partial substring in a component name, a");
+ pw.println(" hex object identifier.");
+ pw.println(" -a: include all available server state.");
+ pw.println(" -c: include client state.");
+ pw.println(" -p: limit output to given package.");
+ } else {
+ pw.println("Activity manager (activity) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>");
+ pw.println(" Complete stop the given application package.");
+ pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
+ pw.println(" Kill all processes associated with the given application.");
+ pw.println(" kill-all");
+ pw.println(" Kill all processes that are safe to kill (cached, etc)");
+ pw.println(" write");
+ pw.println(" Write all pending state to storage.");
+ pw.println(" track-associations");
+ pw.println(" Enable association tracking.");
+ pw.println(" untrack-associations");
+ pw.println(" Disable and clear association tracking.");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 4671cb0..499aba9 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -53,6 +53,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
+import android.view.AppTransitionAnimationSpec;
import android.view.IApplicationToken;
import android.view.WindowManager;
@@ -189,6 +190,7 @@
// and drawable-sw400dp will be added to both as 400.
private int[] mVerticalSizeConfigurations;
private int[] mHorizontalSizeConfigurations;
+ private int[] mSmallestSizeConfigurations;
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
@@ -341,6 +343,10 @@
return crossesSizeThreshold(mVerticalSizeConfigurations, firstDp, secondDp);
}
+ public boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
+ return crossesSizeThreshold(mSmallestSizeConfigurations, firstDp, secondDp);
+ }
+
/**
* The purpose of this method is to decide whether the activity needs to be relaunched upon
* changing its size. In most cases the activities don't need to be relaunched, if the resize
@@ -371,9 +377,10 @@
}
public void setSizeConfigurations(int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations) {
+ int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
mHorizontalSizeConfigurations = horizontalSizeConfiguration;
mVerticalSizeConfigurations = verticalSizeConfigurations;
+ mSmallestSizeConfigurations = smallestSizeConfigurations;
}
static class Token extends IApplicationToken.Stub {
@@ -863,17 +870,24 @@
break;
case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP:
case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
- service.mWindowManager.overridePendingAppTransitionAspectScaledThumb(
- pendingOptions.getThumbnail(),
- pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight(),
- pendingOptions.getOnAnimationStartListener(),
- (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP));
- if (intent.getSourceBounds() == null) {
- intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
- pendingOptions.getStartY(),
- pendingOptions.getStartX() + pendingOptions.getWidth(),
- pendingOptions.getStartY() + pendingOptions.getHeight()));
+ final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
+ if (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
+ && specs != null) {
+ service.mWindowManager.overridePendingAppTransitionMultiThumb(
+ specs, pendingOptions.getOnAnimationStartListener(), false);
+ } else {
+ service.mWindowManager.overridePendingAppTransitionAspectScaledThumb(
+ pendingOptions.getThumbnail(),
+ pendingOptions.getStartX(), pendingOptions.getStartY(),
+ pendingOptions.getWidth(), pendingOptions.getHeight(),
+ pendingOptions.getOnAnimationStartListener(),
+ (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP));
+ if (intent.getSourceBounds() == null) {
+ intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+ pendingOptions.getStartY(),
+ pendingOptions.getStartX() + pendingOptions.getWidth(),
+ pendingOptions.getStartY() + pendingOptions.getHeight()));
+ }
}
break;
default:
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 99539e4..b5f424d 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.HOME_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
import static android.app.ActivityManager.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -529,25 +530,35 @@
* @param task If non-null, the task will be moved to the top of the stack.
* */
void moveToFront(String reason, TaskRecord task) {
- if (isAttached()) {
- final ActivityStack lastFocusStack = mStacks.get(mStacks.size() - 1);
- // Need to move this stack to the front before calling
- // {@link ActivityStackSupervisor#setFocusStack} below.
- mStacks.remove(this);
- mStacks.add(this);
+ if (!isAttached()) {
+ return;
+ }
- // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
- if (isOnHomeDisplay()) {
- mStackSupervisor.setFocusStack(reason, lastFocusStack);
+ mStacks.remove(this);
+ int addIndex = mStacks.size();
+
+ if (addIndex > 0) {
+ final ActivityStack topStack = mStacks.get(addIndex - 1);
+ if (topStack.mStackId == PINNED_STACK_ID && topStack != this) {
+ // The pinned stack is always the top most stack (always-on-top).
+ // So, stack is moved just below the pinned stack.
+ addIndex--;
}
- if (task != null) {
- insertTaskAtTop(task, null);
- } else {
- task = topTask();
- }
- if (task != null) {
- mWindowManager.moveTaskToTop(task.taskId);
- }
+ }
+
+ mStacks.add(addIndex, this);
+
+ // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
+ if (isOnHomeDisplay()) {
+ mStackSupervisor.setFocusStack(reason, this);
+ }
+ if (task != null) {
+ insertTaskAtTop(task, null);
+ } else {
+ task = topTask();
+ }
+ if (task != null) {
+ mWindowManager.moveTaskToTop(task.taskId);
}
}
@@ -1303,12 +1314,7 @@
return false;
}
- if (mStackSupervisor.isFrontStack(this)) {
- return true;
- }
-
- if (mStackId == PINNED_STACK_ID) {
- // Pinned stack is always visible if it exist.
+ if (mStackSupervisor.isFrontStack(this) || mStackSupervisor.isFocusedStack(this)) {
return true;
}
@@ -2040,7 +2046,7 @@
// Have the window manager re-evaluate the orientation of
// the screen based on the new activity order.
boolean notUpdated = true;
- if (mStackSupervisor.isFrontStack(this)) {
+ if (mStackSupervisor.isFocusedStack(this)) {
Configuration config = mWindowManager.updateOrientationFromAppTokens(
mService.mConfiguration,
next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
@@ -2787,7 +2793,7 @@
}
private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
- if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
+ if (mStackSupervisor.isFocusedStack(this) && mService.mFocusedActivity == r) {
ActivityRecord next = topRunningActivityLocked();
final String myReason = reason + " adjustFocus";
if (next != r) {
@@ -3394,7 +3400,7 @@
if (task != null && task.removeActivity(r)) {
if (DEBUG_STACK) Slog.i(TAG_STACK,
"removeActivityFromHistoryLocked: last activity removed from " + this);
- if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
+ if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
task.isOverHomeStack()) {
mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), reason);
}
@@ -4147,9 +4153,7 @@
if ((taskChanges & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
final int oldSmallest = oldTaskOverride.smallestScreenWidthDp;
final int newSmallest = taskConfig.smallestScreenWidthDp;
- final boolean crosses = record.crossesHorizontalSizeThreshold(oldSmallest, newSmallest)
- || record.crossesVerticalSizeThreshold(oldSmallest, newSmallest);
- if (!crosses) {
+ if (!record.crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
taskChanges &= ~ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
}
@@ -4375,6 +4379,7 @@
RunningTaskInfo ci = new RunningTaskInfo();
ci.id = task.taskId;
+ ci.stackId = mStackId;
ci.baseActivity = r.intent.getComponent();
ci.topActivity = top.intent.getComponent();
ci.lastActiveTime = task.lastActiveTime;
@@ -4567,7 +4572,7 @@
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
// We only need to adjust focused stack if this stack is in focus.
- if (isOnHomeDisplay() && mStackSupervisor.isFrontStack(this)) {
+ if (isOnHomeDisplay() && mStackSupervisor.isFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
mStackSupervisor.moveHomeStackToFront(myReason);
@@ -4675,7 +4680,7 @@
return;
}
- final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack)
+ final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
&& (mStackSupervisor.topRunningActivityLocked() == r);
final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 98cfc7c..1cd71a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -458,10 +458,7 @@
return mLastFocusedStack;
}
- /** Top of all visible stacks is/should always be equal to the focused stack.
- * Use {@link ActivityStack#isStackVisibleLocked} to determine if a specific
- * stack is visible or not. */
- boolean isFrontStack(ActivityStack stack) {
+ boolean isFocusedStack(ActivityStack stack) {
if (stack == null) {
return false;
}
@@ -473,18 +470,22 @@
return stack == mFocusedStack;
}
- void setFocusStack(String reason, ActivityStack lastFocusedStack) {
- ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
- final int topNdx = stacks.size() - 1;
- if (topNdx <= 0) {
- return;
+ /** The top most stack. */
+ boolean isFrontStack(ActivityStack stack) {
+ if (stack == null) {
+ return false;
}
- final ActivityStack topStack = stacks.get(topNdx);
- mFocusedStack = topStack;
- if (lastFocusedStack != null) {
- mLastFocusedStack = lastFocusedStack;
+ final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
+ if (parent != null) {
+ stack = parent.task.stack;
}
+ return stack == mHomeStack.mStacks.get((mHomeStack.mStacks.size() - 1));
+ }
+
+ void setFocusStack(String reason, ActivityStack focusedStack) {
+ mLastFocusedStack = mFocusedStack;
+ mFocusedStack = focusedStack;
EventLogTags.writeAmFocusedStack(
mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
@@ -642,7 +643,7 @@
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (!isFrontStack(stack)) {
+ if (!isFocusedStack(stack)) {
continue;
}
ActivityRecord hr = stack.topRunningActivityLocked();
@@ -673,7 +674,7 @@
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (!isFrontStack(stack) || stack.numActivities() == 0) {
+ if (!isFocusedStack(stack) || stack.numActivities() == 0) {
continue;
}
final ActivityRecord resumedActivity = stack.mResumedActivity;
@@ -692,7 +693,7 @@
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
final ActivityRecord r = stack.mResumedActivity;
if (r != null && r.state != RESUMED) {
return false;
@@ -737,7 +738,7 @@
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (!isFrontStack(stack) && stack.mResumedActivity != null) {
+ if (!isFocusedStack(stack) && stack.mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
" mResumedActivity=" + stack.mResumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
@@ -1374,7 +1375,7 @@
// launching the initial activity (that is, home), so that it can have
// a chance to initialize itself while in the background, making the
// switch back to it faster and look better.
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
mService.startSetupActivityLocked();
}
@@ -2621,7 +2622,7 @@
r.idle = true;
//Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
- if (isFrontStack(r.task.stack) || fromTimeout) {
+ if (isFocusedStack(r.task.stack) || fromTimeout) {
booting = checkFinishBootingLocked();
}
}
@@ -2749,8 +2750,7 @@
boolean didSomething = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int numStacks = stacks.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+ for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
if (stack.finishDisabledPackageActivitiesLocked(
packageName, filterByClasses, doit, evenPersistent, userId)) {
@@ -2774,7 +2774,7 @@
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
if (stack.mResumedActivity != null) {
fgApp = stack.mResumedActivity.app;
} else if (stack.mPausingActivity != null) {
@@ -2806,7 +2806,7 @@
}
// Do targetStack first.
boolean result = false;
- if (isFrontStack(targetStack)) {
+ if (isFocusedStack(targetStack)) {
result = targetStack.resumeTopActivityLocked(target, targetOptions);
}
@@ -2818,7 +2818,7 @@
// Already started above.
continue;
}
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
stack.resumeTopActivityLocked(null);
}
}
@@ -2876,13 +2876,16 @@
}
if (stackId != task.stack.mStackId) {
- moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason);
- } else {
- task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
- task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker,
- reason);
+ moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+
+ // moveTaskToStackUncheckedLocked() should already placed the task on top,
+ // still need moveTaskToFrontLocked() below for any transition settings.
}
+ final ActivityRecord r = task.getTopActivity();
+ task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+ r == null ? null : r.appTimeTracker, reason);
+
if (DEBUG_STACK) Slog.d(TAG_STACK,
"findTaskToMoveToFront: moved to front of stack=" + task.stack);
}
@@ -2989,84 +2992,87 @@
}
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
+ mWindowManager.deferSurfaceLayout();
+ try {
+ ActivityRecord r = stack.topRunningActivityLocked();
- ActivityRecord r = stack.topRunningActivityLocked();
-
- mTmpBounds.clear();
- mTmpConfigs.clear();
- ArrayList<TaskRecord> tasks = stack.getAllTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- TaskRecord task = tasks.get(i);
- if (task.mResizeable) {
- if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
- // For freeform stack we don't adjust the size of the tasks to match that
- // of the stack, but we do try to make sure the tasks are still contained
- // with the bounds of the stack.
- tempRect2.set(task.mBounds);
- fitWithinBounds(tempRect2, bounds);
- task.updateOverrideConfiguration(tempRect2);
- } else {
- task.updateOverrideConfiguration(bounds);
- }
- }
-
- mTmpConfigs.put(task.taskId, task.mOverrideConfig);
- mTmpBounds.put(task.taskId, task.mBounds);
- }
- stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds);
- if (stack.mStackId == DOCKED_STACK_ID) {
- // Dock stack funness...Yay!
- if (stack.mFullscreen) {
- // The dock stack went fullscreen which is kinda like dismissing it.
- // In this case we make all other static stacks fullscreen and move all
- // docked stack tasks to the fullscreen stack.
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID && getStack(i) != null) {
- resizeStackLocked(i, null, preserveWindows, true);
+ mTmpBounds.clear();
+ mTmpConfigs.clear();
+ ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ TaskRecord task = tasks.get(i);
+ if (task.mResizeable) {
+ if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ // For freeform stack we don't adjust the size of the tasks to match that
+ // of the stack, but we do try to make sure the tasks are still contained
+ // with the bounds of the stack.
+ tempRect2.set(task.mBounds);
+ fitWithinBounds(tempRect2, bounds);
+ task.updateOverrideConfiguration(tempRect2);
+ } else {
+ task.updateOverrideConfiguration(bounds);
}
}
- final int count = tasks.size();
- for (int i = 0; i < count; i++) {
- moveTaskToStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS);
- }
+ mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+ mTmpBounds.put(task.taskId, task.mBounds);
+ }
+ stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds);
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ // Dock stack funness...Yay!
+ if (stack.mFullscreen) {
+ // The dock stack went fullscreen which is kinda like dismissing it.
+ // In this case we make all other static stacks fullscreen and move all
+ // docked stack tasks to the fullscreen stack.
+ for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+ if (i != DOCKED_STACK_ID && getStack(i) != null) {
+ resizeStackLocked(i, null, preserveWindows, true);
+ }
+ }
- // stack shouldn't contain anymore activities, so nothing to resume.
- r = null;
- } else {
- // Docked stacks occupy a dedicated region on screen so the size of all other
- // 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.
- mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+ final int count = tasks.size();
+ for (int i = 0; i < count; i++) {
+ moveTaskToStackLocked(tasks.get(i).taskId,
+ FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack");
+ }
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID) {
- ActivityStack otherStack = getStack(i);
- if (otherStack != null) {
- resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
+ // stack shouldn't contain anymore activities, so nothing to resume.
+ r = null;
+ } else {
+ // Docked stacks occupy a dedicated region on screen so the size of all other
+ // 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.
+ mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+
+ for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+ if (i != DOCKED_STACK_ID) {
+ ActivityStack otherStack = getStack(i);
+ if (otherStack != null) {
+ resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
+ }
}
}
}
+ // Since we are resizing the stack, all other operations should strive to preserve
+ // windows.
+ preserveWindows = true;
}
- // Since we are resizing the stack, all other operations should strive to preserve
- // windows.
- preserveWindows = true;
- }
- stack.setBounds(bounds);
+ stack.setBounds(bounds);
- if (r != null) {
- final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- ensureActivitiesVisibleLocked(r, 0, preserveWindows);
- if (!updated) {
- resumeTopActivitiesLocked(stack, null, null);
+ if (r != null) {
+ final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows);
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ ensureActivitiesVisibleLocked(r, 0, preserveWindows);
+ if (!updated) {
+ resumeTopActivitiesLocked(stack, null, null);
+ }
}
+ } finally {
+ mWindowManager.continueSurfaceLayout();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
-
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
@@ -3238,7 +3244,7 @@
ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
final ActivityRecord r = task.getTopActivity();
- final boolean wasFocused = isFrontStack(task.stack) && (topRunningActivityLocked() == r);
+ final boolean wasFocused = isFocusedStack(task.stack) && (topRunningActivityLocked() == r);
final boolean wasResumed = wasFocused && (task.stack.mResumedActivity == r);
final boolean resizeable = task.mResizeable;
@@ -3262,13 +3268,13 @@
return stack;
}
- void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus) {
+ void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
+ String reason) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
- final String reason = "moveTaskToStack";
if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
// We are about to relaunch the activity because its configuration changed due to
@@ -3281,7 +3287,8 @@
mWindowManager.setReplacingWindow(r.appToken, true /* animate */);
}
final ActivityStack stack =
- moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, reason);
+ moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
+ "moveTaskToStack:" + reason);
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
@@ -3327,7 +3334,8 @@
if (task.mActivities.size() == 1) {
// There is only one activity in the task. So, we can just move the task over to the
// pinned stack without re-parenting the activity in a different task.
- moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS);
+ moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS,
+ "moveTopActivityToPinnedStack");
} else {
final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
pinnedStack.moveActivityToStack(r);
@@ -3462,7 +3470,7 @@
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
stack.awakeFromSleepingLocked();
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
resumeTopActivitiesLocked();
}
}
@@ -3529,7 +3537,7 @@
boolean reportResumedActivityLocked(ActivityRecord r) {
final ActivityStack stack = r.task.stack;
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
mService.updateUsageStats(r, true);
}
if (allResumedActivitiesComplete()) {
@@ -3822,7 +3830,7 @@
final ActivityStack stack = stacks.get(stackNdx);
final ActivityRecord r = stack.topRunningActivityLocked();
final ActivityState state = r == null ? DESTROYED : r.state;
- if (isFrontStack(stack)) {
+ if (isFocusedStack(stack)) {
if (r == null) Slog.e(TAG,
"validateTop...: null top activity, stack=" + stack);
else {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 090a342..120b40c 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -318,7 +318,7 @@
mNextAffiliateTaskId = nextTaskId;
mCallingUid = callingUid;
mCallingPackage = callingPackage;
- mResizeable = resizeable;
+ mResizeable = resizeable || mService.mForceResizableActivites;
mPrivileged = privileged;
ActivityInfo info = mActivities.get(0).info;
mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -420,7 +420,7 @@
} else {
autoRemoveRecents = false;
}
- mResizeable = info.resizeable;
+ mResizeable = info.resizeable || mService.mForceResizableActivites;
mLockTaskMode = info.lockTaskLaunchMode;
mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
setLockTaskAuth();
@@ -626,6 +626,9 @@
// Only set this based on the first activity
if (mActivities.isEmpty()) {
taskType = r.mActivityType;
+ if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) {
+ mResizeable = r.info.resizeable;
+ }
isPersistable = r.isPersistable();
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c4b57f1..66e731a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -109,6 +109,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -279,7 +280,7 @@
0, // STREAM_MUSIC
0, // STREAM_ALARM
0, // STREAM_NOTIFICATION
- 1, // STREAM_BLUETOOTH_SCO
+ 0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0 // STREAM_TTS
@@ -564,6 +565,27 @@
return "card=" + card + ";device=" + device + ";";
}
+ public static final class Lifecycle extends SystemService {
+ private AudioService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new AudioService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.AUDIO_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mService.systemReady();
+ }
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////
// Construction
///////////////////////////////////////////////////////////////////////////
@@ -781,7 +803,8 @@
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
VolumeStreamState streamState = mStreamStates[streamType];
- AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
+ AudioSystem.initStreamVolume(
+ streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
streamState.applyAllVolumes();
}
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java
index 9149fcc..207c05d 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/services/core/java/com/android/server/input/InputWindowHandle.java
@@ -100,6 +100,17 @@
}
@Override
+ public String toString() {
+ return new StringBuilder(name)
+ .append(", layer=").append(layer)
+ .append(", frame=[").append(frameLeft).append(",").append(frameTop).append(",")
+ .append(frameRight).append(",").append(frameBottom).append("]")
+ .append(", touchableRegion=").append(touchableRegion)
+ .toString();
+
+ }
+
+ @Override
protected void finalize() throws Throwable {
try {
nativeDispose();
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 19d8538..9441d88 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
@@ -29,6 +30,7 @@
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -79,7 +81,7 @@
final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
- c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+ c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
c.clientLabel = R.string.condition_provider_service_binding_label;
@@ -280,6 +282,26 @@
}
}
+ @Override
+ protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+ int userId) {
+ final ContentResolver cr = mContext.getContentResolver();
+ String settingValue = Settings.Secure.getStringForUser(
+ cr,
+ settingName,
+ userId);
+ if (TextUtils.isEmpty(settingValue))
+ return null;
+ String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
+ ArraySet<ComponentName> result = new ArraySet<>(packages.length);
+ for (int i = 0; i < packages.length; i++) {
+ if (!TextUtils.isEmpty(packages[i])) {
+ result.addAll(queryPackageForServices(packages[i], userId));
+ }
+ }
+ return result;
+ }
+
public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
synchronized (mMutex) {
final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index a54a61a..d2a264d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -70,7 +70,7 @@
protected final String TAG = getClass().getSimpleName();
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String ENABLED_SERVICES_SEPARATOR = ":";
+ protected static final String ENABLED_SERVICES_SEPARATOR = ":";
protected final Context mContext;
protected final Object mMutex;
@@ -279,7 +279,8 @@
}
- private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, int userId) {
+ protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+ int userId) {
final ContentResolver cr = mContext.getContentResolver();
String settingValue = Settings.Secure.getStringForUser(
cr,
@@ -319,7 +320,6 @@
userId);
}
-
/**
* Remove access for any services that no longer exist.
*/
@@ -332,18 +332,15 @@
rebuildRestoredPackages();
}
- private void updateSettingsAccordingToInstalledServices(int userId) {
- boolean restoredChanged = false;
- boolean currentChanged = false;
- Set<ComponentName> restored =
- loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
- Set<ComponentName> current =
- loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+ protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
Set<ComponentName> installed = new ArraySet<>();
-
final PackageManager pm = mContext.getPackageManager();
+ Intent queryIntent = new Intent(mConfig.serviceInterface);
+ if (!TextUtils.isEmpty(packageName)) {
+ queryIntent.setPackage(packageName);
+ }
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(mConfig.serviceInterface),
+ queryIntent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
if (DEBUG)
@@ -363,6 +360,18 @@
}
installed.add(component);
}
+ return installed;
+ }
+
+ private void updateSettingsAccordingToInstalledServices(int userId) {
+ boolean restoredChanged = false;
+ boolean currentChanged = false;
+ Set<ComponentName> restored =
+ loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
+ Set<ComponentName> current =
+ loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+ // Load all services for all packages.
+ Set<ComponentName> installed = queryPackageForServices(null, userId);
ArraySet<ComponentName> retained = new ArraySet<>();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e8e46ef..b84811f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1579,12 +1579,6 @@
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config, String reason) {
- checkCallerIsSystem();
- return mZenModeHelper.setConfig(config, reason);
- }
-
- @Override
public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
@@ -1669,12 +1663,6 @@
});
}
- @Override
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
- enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
- mZenModeHelper.requestZenModeConditions(callback, relevance);
- }
-
private void enforceSystemOrSystemUIOrVolume(String message) {
if (mAudioManagerInternal != null) {
final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
@@ -2146,13 +2134,6 @@
+ " id=" + id + " notification=" + notification);
}
- if (notification.getSmallIcon() != null) {
- if (!notification.isValid()) {
- throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
- + " id=" + id + " notification=" + notification);
- }
- }
-
mHandler.post(new Runnable() {
@Override
public void run() {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 76c6443..a1f8c41 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -121,8 +121,11 @@
public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
- validator, contactsTimeoutMs, timeoutAffinity);
+ synchronized (mConfig) {
+ return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle,
+ extras,
+ validator, contactsTimeoutMs, timeoutAffinity);
+ }
}
public boolean isCall(NotificationRecord record) {
@@ -130,7 +133,9 @@
}
public boolean shouldIntercept(NotificationRecord record) {
- return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+ synchronized (mConfig) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+ }
}
public void addCallback(Callback callback) {
@@ -175,10 +180,6 @@
mConfigs.remove(user);
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
- mConditions.requestConditions(callback, relevance);
- }
-
public int getZenModeListenerInterruptionFilter() {
return NotificationManager.zenModeToInterruptionFilter(mZenMode);
}
@@ -203,18 +204,23 @@
public List<AutomaticZenRule> getAutomaticZenRules() {
List<AutomaticZenRule> rules = new ArrayList<>();
- if (mConfig == null) return rules;
- for(ZenRule rule : mConfig.automaticRules.values()) {
- if (canManageAutomaticZenRule(rule)) {
- rules.add(createAutomaticZenRule(rule));
+ synchronized (mConfig) {
+ if (mConfig == null) return rules;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (canManageAutomaticZenRule(rule)) {
+ rules.add(createAutomaticZenRule(rule));
+ }
}
}
return rules;
}
public AutomaticZenRule getAutomaticZenRule(String id) {
- if (mConfig == null) return null;
- ZenRule rule = mConfig.automaticRules.get(id);
+ ZenRule rule;
+ synchronized (mConfig) {
+ if (mConfig == null) return null;
+ rule = mConfig.automaticRules.get(id);
+ }
if (rule == null) return null;
if (canManageAutomaticZenRule(rule)) {
return createAutomaticZenRule(rule);
@@ -223,14 +229,18 @@
}
public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
- if (mConfig == null) return null;
- if (DEBUG) {
- Log.d(TAG, "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" +reason);
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return null;
+ if (DEBUG) {
+ Log.d(TAG,
+ "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" + reason);
+ }
+ if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+ throw new IllegalArgumentException("Rule already exists");
+ }
+ newConfig = mConfig.copy();
}
- if (!TextUtils.isEmpty(automaticZenRule.getId())) {
- throw new IllegalArgumentException("Rule already exists");
- }
- final ZenModeConfig newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
populateZenRule(automaticZenRule, rule, true);
newConfig.automaticRules.put(rule.id, rule);
@@ -242,12 +252,15 @@
}
public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
- if (mConfig == null) return false;
- if (DEBUG) {
- Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
- + " reason=" + reason);
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return false;
+ if (DEBUG) {
+ Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ + " reason=" + reason);
+ }
+ newConfig = mConfig.copy();
}
- final ZenModeConfig newConfig = mConfig.copy();
final String ruleId = automaticZenRule.getId();
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
if (ruleId == null) {
@@ -265,8 +278,11 @@
}
public boolean removeAutomaticZenRule(String id, String reason) {
- if (mConfig == null) return false;
- final ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return false;
+ newConfig = mConfig.copy();
+ }
ZenRule rule = newConfig.automaticRules.get(id);
if (rule == null) return false;
if (canManageAutomaticZenRule(rule)) {
@@ -328,12 +344,15 @@
private void setManualZenMode(int zenMode, Uri conditionId, String reason,
boolean setRingerMode) {
- if (mConfig == null) return;
- if (!Global.isValidZenMode(zenMode)) return;
- if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
- + " conditionId=" + conditionId + " reason=" + reason
- + " setRingerMode=" + setRingerMode);
- final ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ newConfig = mConfig.copy();
+ }
if (zenMode == Global.ZEN_MODE_OFF) {
newConfig.manualRule = null;
for (ZenRule automaticRule : newConfig.automaticRules.values()) {
@@ -360,7 +379,9 @@
dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
}
pw.print(prefix); pw.print("mUser="); pw.println(mUser);
- dump(pw, prefix, "mConfig", mConfig);
+ synchronized (mConfig) {
+ dump(pw, prefix, "mConfig", mConfig);
+ }
pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
mFiltering.dump(pw, prefix);
mConditions.dump(pw, prefix);
@@ -437,7 +458,9 @@
}
public ZenModeConfig getConfig() {
- return mConfig;
+ synchronized (mConfig) {
+ return mConfig.copy();
+ }
}
public boolean setConfig(ZenModeConfig config, String reason) {
@@ -462,19 +485,21 @@
return true;
}
mConditions.evaluateConfig(config, false /*processSubscriptions*/); // may modify config
- mConfigs.put(config.user, config);
- if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
- ZenLog.traceConfig(reason, mConfig, config);
- final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
- getNotificationPolicy(config));
- mConfig = config;
- if (config.equals(mConfig)) {
- dispatchOnConfigChanged();
+ synchronized (mConfig) {
+ mConfigs.put(config.user, config);
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+ ZenLog.traceConfig(reason, mConfig, config);
+ final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+ getNotificationPolicy(config));
+ mConfig = config;
+ if (config.equals(mConfig)) {
+ dispatchOnConfigChanged();
+ }
+ if (policyChanged) {
+ dispatchOnPolicyChanged();
+ }
}
- if (policyChanged){
- dispatchOnPolicyChanged();
- }
- final String val = Integer.toString(mConfig.hashCode());
+ final String val = Integer.toString(config.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
if (!evaluateZenMode(reason, setRingerMode)) {
applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
@@ -529,17 +554,19 @@
}
private int computeZenMode() {
- if (mConfig == null) return Global.ZEN_MODE_OFF;
- if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
- int zen = Global.ZEN_MODE_OFF;
- for (ZenRule automaticRule : mConfig.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- zen = automaticRule.zenMode;
+ synchronized (mConfig) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
}
}
+ return zen;
}
- return zen;
}
private void applyRestrictions() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aed2f0b..be9f44dc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1753,11 +1753,21 @@
PermissionsState permissionsState = sb.getPermissionsState();
- for (String permission : pkg.requestedPermissions) {
- BasePermission bp = mSettings.mPermissions.get(permission);
- if (bp != null && bp.isRuntime() && (grantedPermissions == null
- || ArrayUtils.contains(grantedPermissions, permission))) {
- permissionsState.grantRuntimePermission(bp, userId);
+ final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+ synchronized (mPackages) {
+ for (String permission : pkg.requestedPermissions) {
+ BasePermission bp = mSettings.mPermissions.get(permission);
+ if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+ && (grantedPermissions == null
+ || ArrayUtils.contains(grantedPermissions, permission))) {
+ final int flags = permissionsState.getPermissionFlags(permission, userId);
+ // Installer cannot change immutable permissions.
+ if ((flags & immutableFlags) == 0) {
+ grantRuntimePermission(pkg.packageName, permission, userId);
+ }
+ }
}
}
}
@@ -3558,7 +3568,8 @@
killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
}
});
- } break;
+ }
+ break;
}
mOnPermissionChangeListeners.onPermissionsChanged(uid);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b36a22e..4dd7388 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,14 +16,15 @@
package com.android.server.pm;
-import android.accounts.Account;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -46,6 +47,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.system.ErrnoException;
@@ -59,9 +61,11 @@
import android.util.TimeUtils;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
@@ -83,11 +87,18 @@
import libcore.io.IoUtils;
+/**
+ * Service for {@link UserManager}.
+ *
+ * Method naming convention:
+ * - Methods suffixed with "Locked" should be called within the {@code this} lock.
+ * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ */
public class UserManagerService extends IUserManager.Stub {
private static final String LOG_TAG = "UserManagerService";
- private static final boolean DBG = false;
+ private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
private static final String ATTR_FLAGS = "flags";
@@ -160,7 +171,38 @@
private final File mUserListFile;
private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
- private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
+
+ private final Object mRestrictionsLock = new Object();
+
+ /**
+ * User restrictions set via UserManager. This doesn't include restrictions set by
+ * device owner / profile owners.
+ *
+ * DO NOT Change existing {@link Bundle} in it. When changing a restriction for a user,
+ * a new {@link Bundle} should always be created and set. This is because a {@link Bundle}
+ * maybe shared between {@link #mBaseUserRestrictions} and
+ * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+ * (Otherwise we won't be able to detect what restrictions have changed in
+ * {@link #updateUserRestrictionsInternalRL).
+ */
+ @GuardedBy("mRestrictionsLock")
+ private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
+
+ /**
+ * Cached user restrictions that are in effect -- i.e. {@link #mBaseUserRestrictions} combined
+ * with device / profile owner restrictions. We'll initialize it lazily; use
+ * {@link #getEffectiveUserRestrictions} to access it.
+ *
+ * DO NOT Change existing {@link Bundle} in it. When changing a restriction for a user,
+ * a new {@link Bundle} should always be created and set. This is because a {@link Bundle}
+ * maybe shared between {@link #mBaseUserRestrictions} and
+ * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
+ * (Otherwise we won't be able to detect what restrictions have changed in
+ * {@link #updateUserRestrictionsInternalRL).
+ */
+ @GuardedBy("mRestrictionsLock")
+ private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
+
private final Bundle mGuestRestrictions = new Bundle();
/**
@@ -176,6 +218,8 @@
private IAppOpsService mAppOpsService;
+ private final LocalService mLocalService;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -231,6 +275,8 @@
sInstance = this;
}
}
+ mLocalService = new LocalService();
+ LocalServices.addService(UserManagerInternal.class, mLocalService);
}
void systemReady() {
@@ -258,8 +304,9 @@
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
for (int i = 0; i < mUserIds.length; ++i) {
+ final int userId = mUserIds[i];
try {
- mAppOpsService.setUserRestrictions(mUserRestrictions.get(mUserIds[i]), mUserIds[i]);
+ mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
}
@@ -588,75 +635,171 @@
}
}
- @Override
- public boolean hasUserRestriction(String restrictionKey, int userId) {
- synchronized (mPackagesLock) {
- Bundle restrictions = mUserRestrictions.get(userId);
- return restrictions != null && restrictions.getBoolean(restrictionKey);
+ @GuardedBy("mRestrictionsLock")
+ private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+ final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+
+ final Bundle effective;
+ if (dpmi == null) {
+ // TODO Make sure it's because DPMS is disabled and not because we called it too early.
+ effective = systemRestrictions;
+ } else {
+ effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+ }
+ return effective;
+ }
+
+ @GuardedBy("mRestrictionsLock")
+ private void invalidateEffectiveUserRestrictionsRL(int userId) {
+ if (DBG) {
+ Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+ }
+ mCachedEffectiveUserRestrictions.remove(userId);
+ }
+
+ private Bundle getEffectiveUserRestrictions(int userId) {
+ synchronized (mRestrictionsLock) {
+ Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
+ if (restrictions == null) {
+ restrictions = computeEffectiveUserRestrictionsRL(userId);
+ mCachedEffectiveUserRestrictions.put(userId, restrictions);
+ }
+ return restrictions;
}
}
+ /** @return a specific user restriction that's in effect currently. */
+ @Override
+ public boolean hasUserRestriction(String restrictionKey, int userId) {
+ Bundle restrictions = getEffectiveUserRestrictions(userId);
+ return restrictions != null && restrictions.getBoolean(restrictionKey);
+ }
+
+ /**
+ * @return UserRestrictions that are in effect currently. This always returns a new
+ * {@link Bundle}.
+ */
@Override
public Bundle getUserRestrictions(int userId) {
- synchronized (mPackagesLock) {
- Bundle restrictions = mUserRestrictions.get(userId);
- return restrictions != null ? new Bundle(restrictions) : new Bundle();
- }
+ Bundle restrictions = getEffectiveUserRestrictions(userId);
+ return restrictions != null ? new Bundle(restrictions) : new Bundle();
}
@Override
public void setUserRestriction(String key, boolean value, int userId) {
checkManageUsersPermission("setUserRestriction");
- synchronized (mPackagesLock) {
- if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
- Bundle restrictions = getUserRestrictions(userId);
- restrictions.putBoolean(key, value);
- setUserRestrictionsInternalLocked(restrictions, userId);
- }
+ if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+ setUserRestrictionNoCheck(key, value, userId);
}
}
@Override
public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
checkSystemOrRoot("setSystemControlledUserRestriction");
- synchronized (mPackagesLock) {
- Bundle restrictions = getUserRestrictions(userId);
- restrictions.putBoolean(key, value);
- setUserRestrictionsInternalLocked(restrictions, userId);
+ setUserRestrictionNoCheck(key, value, userId);
+ }
+
+ private void setUserRestrictionNoCheck(String key, boolean value, int userId) {
+ synchronized (mRestrictionsLock) {
+ // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
+ // a copy.
+ final Bundle newRestrictions = new Bundle();
+ UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+ newRestrictions.putBoolean(key, value);
+
+ updateUserRestrictionsInternalRL(newRestrictions, userId);
}
}
- @Override
- public void setUserRestrictions(Bundle restrictions, int userId) {
- checkManageUsersPermission("setUserRestrictions");
- if (restrictions == null) return;
-
- synchronized (mPackagesLock) {
- final Bundle oldUserRestrictions = mUserRestrictions.get(userId);
- // Restore the original state of system controlled restrictions from oldUserRestrictions
- for (String key : UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS) {
- restrictions.remove(key);
- if (oldUserRestrictions.containsKey(key)) {
- restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key));
- }
- }
- setUserRestrictionsInternalLocked(restrictions, userId);
+ /**
+ * Optionally updating user restrictions, calculate the effective user restrictions by
+ * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
+ * apply it to {@link com.android.server.AppOpsService}.
+ * TODO applyUserRestrictionsLocked() should also apply to system settings.
+ *
+ * @param newRestrictions User restrictions to set. If null, only the effective restrictions
+ * will be updated. Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
+ * or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+ * @param userId target user ID.
+ */
+ @GuardedBy("mRestrictionsLock")
+ private void updateUserRestrictionsInternalRL(
+ @Nullable Bundle newRestrictions, int userId) {
+ if (DBG) {
+ Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
+ + " bundle=" + newRestrictions);
}
+ final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
+
+ // Update system restrictions.
+ if (newRestrictions != null) {
+ // If newRestrictions == the current one, it's probably a bug.
+ Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+ Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
+ != newRestrictions);
+ mBaseUserRestrictions.put(userId, newRestrictions);
+ }
+
+ mCachedEffectiveUserRestrictions.put(
+ userId, computeEffectiveUserRestrictionsRL(userId));
+
+ applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
}
- private void setUserRestrictionsInternalLocked(Bundle restrictions, int userId) {
- final Bundle userRestrictions = mUserRestrictions.get(userId);
- userRestrictions.clear();
- userRestrictions.putAll(restrictions);
- long token = Binder.clearCallingIdentity();
+ @GuardedBy("mRestrictionsLock")
+ private void applyUserRestrictionsRL(int userId,
+ Bundle newRestrictions, Bundle prevRestrictions) {
+ final long token = Binder.clearCallingIdentity();
try {
- mAppOpsService.setUserRestrictions(userRestrictions, userId);
+ mAppOpsService.setUserRestrictions(newRestrictions, userId);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
} finally {
Binder.restoreCallingIdentity(token);
}
- scheduleWriteUserLocked(mUsers.get(userId));
+
+ // TODO Move the code from DPMS.setUserRestriction().
+ }
+
+ @GuardedBy("mRestrictionsLock")
+ private void updateEffectiveUserRestrictionsRL(int userId) {
+ updateUserRestrictionsInternalRL(null, userId);
+ }
+
+ @GuardedBy("mRestrictionsLock")
+ private void updateEffectiveUserRestrictionsForAllUsersRL() {
+ // First, invalidate all cached values.
+ synchronized (mRestrictionsLock) {
+ mCachedEffectiveUserRestrictions.clear();
+ }
+ // We don't want to call into ActivityManagerNative while taking a lock, so we'll call
+ // it on a handler.
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ // Then get the list of running users.
+ final int[] runningUsers;
+ try {
+ runningUsers = ActivityManagerNative.getDefault().getRunningUserIds();
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Unable to access ActivityManagerNative");
+ return;
+ }
+ // Then re-calculate the effective restrictions and apply, only for running users.
+ // It's okay if a new user has started after the getRunningUserIds() call,
+ // because we'll do the same thing (re-calculate the restrictions and apply)
+ // when we start a user.
+ // TODO: "Apply restrictions upon user start hasn't been implemented. Implement it.
+ synchronized (mRestrictionsLock) {
+ for (int i = 0; i < runningUsers.length; i++) {
+ updateUserRestrictionsInternalRL(null, runningUsers[i]);
+ }
+ }
+ }
+ };
+ mHandler.post(r);
}
/**
@@ -926,7 +1069,9 @@
mUserVersion = USER_VERSION;
Bundle restrictions = new Bundle();
- mUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+ }
updateUserIdsLocked();
initDefaultGuestRestrictions();
@@ -989,9 +1134,13 @@
serializer.startTag(null, TAG_NAME);
serializer.text(userInfo.name);
serializer.endTag(null, TAG_NAME);
- Bundle restrictions = mUserRestrictions.get(userInfo.id);
+ Bundle restrictions;
+ synchronized (mRestrictionsLock) {
+ restrictions = mBaseUserRestrictions.get(userInfo.id);
+ }
if (restrictions != null) {
- UserRestrictionsUtils.writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+ UserRestrictionsUtils
+ .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_USER);
@@ -1131,7 +1280,9 @@
userInfo.guestToRemove = guestToRemove;
userInfo.profileGroupId = profileGroupId;
userInfo.restrictedProfileParentId = restrictedProfileParentId;
- mUserRestrictions.append(id, restrictions);
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(id, restrictions);
+ }
return userInfo;
} catch (IOException ioe) {
@@ -1333,7 +1484,9 @@
scheduleWriteUserLocked(userInfo);
updateUserIdsLocked();
Bundle restrictions = new Bundle();
- mUserRestrictions.append(userId, restrictions);
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(userId, restrictions);
+ }
}
}
mPm.newUserCreated(userId);
@@ -1616,25 +1769,6 @@
}
}
- @Override
- public void removeRestrictions() {
- checkManageUsersPermission("remove restrictions");
- final int userHandle = UserHandle.getCallingUserId();
- removeRestrictionsForUser(userHandle, true);
- }
-
- private void removeRestrictionsForUser(final int userHandle, boolean unhideApps) {
- synchronized (mPackagesLock) {
- // Remove all user restrictions
- setUserRestrictions(new Bundle(), userHandle);
- // Remove any app restrictions
- cleanAppRestrictions(userHandle);
- }
- if (unhideApps) {
- unhideAllInstalledAppsForUser(userHandle);
- }
- }
-
private void unhideAllInstalledAppsForUser(final int userHandle) {
mHandler.post(new Runnable() {
@Override
@@ -2062,7 +2196,10 @@
}
pw.println(" Restrictions:");
UserRestrictionsUtils.dumpRestrictions(
- pw, " ", mUserRestrictions.get(user.id));
+ pw, " ", mBaseUserRestrictions.get(user.id));
+ pw.println(" Effective restrictions:");
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
}
pw.println();
pw.println("Guest restrictions:");
@@ -2095,4 +2232,49 @@
boolean isInitialized(int userId) {
return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
}
+
+ private class LocalService extends UserManagerInternal {
+
+ @Override
+ public Object getUserRestrictionsLock() {
+ return mRestrictionsLock;
+ }
+
+ @Override
+ @GuardedBy("mRestrictionsLock")
+ public void updateEffectiveUserRestrictionsRL(int userId) {
+ UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+ }
+
+ @Override
+ @GuardedBy("mRestrictionsLock")
+ public void updateEffectiveUserRestrictionsForAllUsersRL() {
+ UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+ }
+
+ @Override
+ public Bundle getBaseUserRestrictions(int userId) {
+ synchronized (mRestrictionsLock) {
+ return mBaseUserRestrictions.get(userId);
+ }
+ }
+
+ @Override
+ public void setBaseUserRestrictionsByDpmsForMigration(
+ int userId, Bundle baseRestrictions) {
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
+ invalidateEffectiveUserRestrictionsRL(userId);
+ }
+
+ synchronized (mPackagesLock) {
+ final UserInfo userInfo = mUsers.get(userId);
+ if (userInfo != null) {
+ writeUserLocked(userInfo);
+ } else {
+ Slog.w(LOG_TAG, "UserInfo not found for " + userId);
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index db1fd2e..23e3b35 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -86,7 +86,6 @@
String tag) throws IOException {
serializer.startTag(null, tag);
for (String key : USER_RESTRICTIONS) {
- //
if (restrictions.getBoolean(key)
&& !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
serializer.attribute(null, key, "true");
@@ -105,6 +104,17 @@
}
}
+ public static void merge(Bundle dest, Bundle in) {
+ if (in == null) {
+ return;
+ }
+ for (String key : in.keySet()) {
+ if (in.getBoolean(key, false)) {
+ dest.putBoolean(key, true);
+ }
+ }
+ }
+
public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
boolean noneSet = true;
if (restrictions != null) {
@@ -114,9 +124,11 @@
noneSet = false;
}
}
- }
- if (noneSet) {
- pw.println(prefix + "none");
+ if (noneSet) {
+ pw.println(prefix + "none");
+ }
+ } else {
+ pw.println(prefix + "null");
}
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index a5d0c2d..eb0ca23 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -64,7 +64,6 @@
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
-import android.view.animation.TranslateYAnimation;
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
@@ -175,7 +174,7 @@
private Rect mTmpFromClipRect = new Rect();
private Rect mTmpToClipRect = new Rect();
- private final Rect mTmpStartRect = new Rect();
+ private final Rect mTmpRect = new Rect();
private final static int APP_STATE_IDLE = 0;
private final static int APP_STATE_READY = 1;
@@ -456,17 +455,19 @@
return -startPos / denom;
}
- private Animation createScaleUpAnimationLocked(
- int transit, boolean enter, int appWidth, int appHeight) {
- Animation a = null;
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+ Rect containingFrame) {
+ Animation a;
+ getDefaultNextAppTransitionStartRect(mTmpRect);
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
if (enter) {
// Entering app zooms out from the center of the initial rect.
- float scaleW = mTmpStartRect.width() / (float) appWidth;
- float scaleH = mTmpStartRect.height() / (float) appHeight;
+ float scaleW = mTmpRect.width() / (float) appWidth;
+ float scaleH = mTmpRect.height() / (float) appHeight;
Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mTmpStartRect.left, scaleW),
- computePivot(mTmpStartRect.right, scaleH));
+ computePivot(mTmpRect.left, scaleW),
+ computePivot(mTmpRect.right, scaleH));
scale.setInterpolator(mDecelerateInterpolator);
Animation alpha = new AlphaAnimation(0, 1);
@@ -543,20 +544,20 @@
final int appWidth = appFrame.width();
final int appHeight = appFrame.height();
- // mTmpStartRect will contain an area around the launcher icon that was pressed. We will
+ // mTmpRect will contain an area around the launcher icon that was pressed. We will
// clip reveal from that area in the final area of the app.
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ getDefaultNextAppTransitionStartRect(mTmpRect);
float t = 0f;
if (appHeight > 0) {
- t = (float) mTmpStartRect.left / appHeight;
+ t = (float) mTmpRect.left / appHeight;
}
int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t);
- int centerX = mTmpStartRect.centerX();
- int centerY = mTmpStartRect.centerY();
- int halfWidth = mTmpStartRect.width() / 2;
- int halfHeight = mTmpStartRect.height() / 2;
+ int centerX = mTmpRect.centerX();
+ int centerY = mTmpRect.centerY();
+ int halfWidth = mTmpRect.width() / 2;
+ int halfHeight = mTmpRect.height() / 2;
// Clip third of the from size of launch icon, expand to full width/height
Animation clipAnimLR = new ClipRectLRAnimation(
@@ -691,19 +692,19 @@
float scaleW = appWidth / thumbWidth;
float unscaledHeight = thumbHeight * scaleW;
- getNextAppTransitionStartRect(taskId, mTmpStartRect);
- float unscaledStartY = mTmpStartRect.top - (unscaledHeight - thumbHeight) / 2f;
+ getNextAppTransitionStartRect(taskId, mTmpRect);
+ float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
if (mNextAppTransitionScaleUp) {
// Animation up from the thumbnail to the full screen
Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
- mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
+ mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
scale.setInterpolator(mTouchResponseInterpolator);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(1, 0);
alpha.setInterpolator(mThumbnailFadeOutInterpolator);
alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
final float toX = appRect.left + appRect.width() / 2 -
- (mTmpStartRect.left + thumbWidth / 2);
+ (mTmpRect.left + thumbWidth / 2);
final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
Animation translate = new TranslateAnimation(0, toX, 0, toY);
translate.setInterpolator(mTouchResponseInterpolator);
@@ -718,7 +719,7 @@
} else {
// Animation down from the full screen to the thumbnail
Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
- mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
+ mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
scale.setInterpolator(mTouchResponseInterpolator);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(0f, 1f);
@@ -746,14 +747,15 @@
* activity that is leaving, and the activity that is entering.
*/
Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
- int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
- Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
- int taskId) {
+ int orientation, int transit, Rect containingFrame, Rect contentInsets,
+ @Nullable Rect surfaceInsets, boolean freeform, int taskId) {
Animation a;
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
- final int thumbWidthI = mTmpStartRect.width();
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
+ getDefaultNextAppTransitionStartRect(mTmpRect);
+ final int thumbWidthI = mTmpRect.width();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
- final int thumbHeightI = mTmpStartRect.height();
+ final int thumbHeightI = mTmpRect.height();
final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
// Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
@@ -762,8 +764,8 @@
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
- if (resizedWindow) {
- a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
+ if (freeform) {
+ a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
containingFrame, surfaceInsets, taskId);
} else {
mTmpFromClipRect.set(containingFrame);
@@ -794,8 +796,8 @@
mNextAppTransitionInsets.set(contentInsets);
Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
- computePivot(mTmpStartRect.left, scale),
- computePivot(mTmpStartRect.top, scale));
+ computePivot(mTmpRect.left, scale),
+ computePivot(mTmpRect.top, scale));
Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
@@ -831,44 +833,49 @@
}
case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
// App window scaling down from full screen
- mTmpFromClipRect.set(containingFrame);
- mTmpToClipRect.set(containingFrame);
- // exclude top screen decor (status bar) region from the destination clip.
- mTmpToClipRect.top = contentInsets.top;
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- // In portrait, we scale the width and clip to the top/left square
- scale = thumbWidth / appWidth;
- scaledTopDecor = (int) (scale * contentInsets.top);
- int unscaledThumbHeight = (int) (thumbHeight / scale);
- mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
+ if (freeform) {
+ a = createAspectScaledThumbnailExitFreeformAnimationLocked(
+ containingFrame, surfaceInsets, taskId);
} else {
- // In landscape, we scale the height and clip to the top/left square. We only
- // scale the part that is not covered by status bar and the nav bar.
- scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
- scaledTopDecor = (int) (scale * contentInsets.top);
- int unscaledThumbWidth = (int) (thumbWidth / scale);
- mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
- // This removes the navigation bar from the last frame, so it better matches the
- // thumbnail. We need to do this explicitly in landscape, because in portrait we
- // already crop vertically.
- mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
+ mTmpFromClipRect.set(containingFrame);
+ mTmpToClipRect.set(containingFrame);
+ // exclude top screen decor (status bar) region from the destination clip.
+ mTmpToClipRect.top = contentInsets.top;
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // In portrait, we scale the width and clip to the top/left square
+ scale = thumbWidth / appWidth;
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
+ } else {
+ // In landscape, we scale the height and clip to the top/left square. We only
+ // scale the part that is not covered by status bar and the nav bar.
+ scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbWidth = (int) (thumbWidth / scale);
+ mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
+ // This removes the navigation bar from the last frame, so it better matches the
+ // thumbnail. We need to do this explicitly in landscape, because in portrait we
+ // already crop vertically.
+ mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
+ }
+
+ mNextAppTransitionInsets.set(contentInsets);
+
+ Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
+ computePivot(mTmpRect.left, scale),
+ computePivot(mTmpRect.top, scale));
+ Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+ Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
+
+ AnimationSet set = new AnimationSet(true);
+ set.addAnimation(clipAnim);
+ set.addAnimation(scaleAnim);
+ set.addAnimation(translateAnim);
+
+ a = set;
+ a.setZAdjustment(Animation.ZORDER_TOP);
}
-
- mNextAppTransitionInsets.set(contentInsets);
-
- Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
- computePivot(mTmpStartRect.left, scale),
- computePivot(mTmpStartRect.top, scale));
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
-
- AnimationSet set = new AnimationSet(true);
- set.addAnimation(clipAnim);
- set.addAnimation(scaleAnim);
- set.addAnimation(translateAnim);
-
- a = set;
- a.setZAdjustment(Animation.ZORDER_TOP);
break;
}
default:
@@ -881,27 +888,48 @@
mTouchResponseInterpolator);
}
- private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
- Rect frame, @Nullable Rect surfaceInsets, int taskId) {
- getNextAppTransitionStartRect(taskId, mTmpStartRect);
- float width = frame.width();
- float height = frame.height();
- float scaleWidth = mTmpStartRect.width() / width;
- float scaleHeight = mTmpStartRect.height() / height;
+ private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
+ @Nullable Rect surfaceInsets, int taskId) {
+ getNextAppTransitionStartRect(taskId, mTmpRect);
+ return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
+ true);
+ }
+
+ private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
+ @Nullable Rect surfaceInsets, int taskId) {
+ getNextAppTransitionStartRect(taskId, mTmpRect);
+ return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
+ false);
+ }
+
+ private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
+ Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
+ final float sourceWidth = sourceFrame.width();
+ final float sourceHeight = sourceFrame.height();
+ final float destWidth = destFrame.width();
+ final float destHeight = destFrame.height();
+ final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
+ final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
AnimationSet set = new AnimationSet(true);
- int surfaceInsetsHorizontal = surfaceInsets == null
+ final int surfaceInsetsH = surfaceInsets == null
? 0 : surfaceInsets.left + surfaceInsets.right;
- int surfaceInsetsVertical = surfaceInsets == null
+ final int surfaceInsetsV = surfaceInsets == null
? 0 : surfaceInsets.top + surfaceInsets.bottom;
// We want the scaling to happen from the center of the surface. In order to achieve that,
// we need to account for surface insets that will be used to enlarge the surface.
- ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1,
- (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2);
- int fromX = mTmpStartRect.left + mTmpStartRect.width() / 2
- - (frame.left + frame.width() / 2);
- int fromY = mTmpStartRect.top + mTmpStartRect.height() / 2
- - (frame.top + frame.height() / 2);
- TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0);
+ final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
+ final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
+ final ScaleAnimation scale = enter ?
+ new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
+ : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
+ final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
+ final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
+ final int destHCenter = destFrame.left + destFrame.width() / 2;
+ final int destVCenter = destFrame.top + destFrame.height() / 2;
+ final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
+ final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
+ final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
+ : new TranslateAnimation(0, fromX, 0, fromY);
set.addAnimation(scale);
set.addAnimation(translation);
return set;
@@ -914,7 +942,7 @@
Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
Bitmap thumbnailHeader) {
Animation a;
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ getDefaultNextAppTransitionStartRect(mTmpRect);
final int thumbWidthI = thumbnailHeader.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
final int thumbHeightI = thumbnailHeader.getHeight();
@@ -925,8 +953,8 @@
float scaleW = appWidth / thumbWidth;
float scaleH = appHeight / thumbHeight;
Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- computePivot(mTmpStartRect.left, 1 / scaleW),
- computePivot(mTmpStartRect.top, 1 / scaleH));
+ computePivot(mTmpRect.left, 1 / scaleW),
+ computePivot(mTmpRect.top, 1 / scaleH));
scale.setInterpolator(mDecelerateInterpolator);
Animation alpha = new AlphaAnimation(1, 0);
@@ -942,8 +970,8 @@
float scaleW = appWidth / thumbWidth;
float scaleH = appHeight / thumbHeight;
a = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mTmpStartRect.left, 1 / scaleW),
- computePivot(mTmpStartRect.top, 1 / scaleH));
+ computePivot(mTmpRect.left, 1 / scaleW),
+ computePivot(mTmpRect.top, 1 / scaleH));
}
return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
@@ -953,11 +981,13 @@
* This animation is created when we are doing a thumbnail transition, for the activity that is
* leaving, and the activity that is entering.
*/
- Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
- int appHeight, int transit, int taskId) {
+ Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
+ int transit, int taskId) {
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
Animation a;
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ getDefaultNextAppTransitionStartRect(mTmpRect);
final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
@@ -969,8 +999,8 @@
float scaleW = thumbWidth / appWidth;
float scaleH = thumbHeight / appHeight;
a = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mTmpStartRect.left, scaleW),
- computePivot(mTmpStartRect.top, scaleH));
+ computePivot(mTmpRect.left, scaleW),
+ computePivot(mTmpRect.top, scaleH));
break;
}
case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
@@ -997,8 +1027,8 @@
float scaleW = thumbWidth / appWidth;
float scaleH = thumbHeight / appHeight;
Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- computePivot(mTmpStartRect.left, scaleW),
- computePivot(mTmpStartRect.top, scaleH));
+ computePivot(mTmpRect.left, scaleW),
+ computePivot(mTmpRect.top, scaleH));
Animation alpha = new AlphaAnimation(1, 0);
@@ -1016,13 +1046,13 @@
return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
}
- private Animation createRelaunchAnimation(int appWidth, int appHeight,
- Rect containingFrame, Rect contentInsets) {
+ private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
final int left = mTmpFromClipRect.left;
final int top = mTmpFromClipRect.top;
mTmpFromClipRect.offset(-left, -top);
- mTmpToClipRect.set(0, 0, appWidth, appHeight);
+ // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+ mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
AnimationSet set = new AnimationSet(true);
float fromWidth = mTmpFromClipRect.width();
float toWidth = mTmpToClipRect.width();
@@ -1069,10 +1099,30 @@
&& mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL;
}
+ /**
+ *
+ * @param frame These are the bounds of the window when it finishes the animation. This is where
+ * the animation must usually finish in entrance animation, as the next frame will
+ * display the window at these coordinates. In case of exit animation, this is
+ * where the animation must start, as the frame before the animation is displaying
+ * the window at these bounds.
+ * @param insets Knowing where the window will be positioned is not enough. Some parts of the
+ * window might be obscured, usually by the system windows (status bar and
+ * navigation bar) and we use content insets to convey that information. This
+ * usually affects the animation aspects vertically, as the system decoration is
+ * at the top and the bottom. For example when we animate from full screen to
+ * recents, we want to exclude the covered parts, because they won't match the
+ * thumbnail after the last frame is executed.
+ * @param surfaceInsets In rare situation the surface is larger than the content and we need to
+ * know about this to make the animation frames match. We currently use
+ * this for freeform windows, which have larger surfaces to display
+ * shadows. When we animate them from recents, we want to match the content
+ * to the recents thumbnail and hence need to account for the surface being
+ * bigger.
+ */
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
- @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
- boolean resizedWindow, int taskId) {
+ int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
+ boolean isVoiceInteraction, boolean freeform, int taskId) {
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
@@ -1095,7 +1145,7 @@
+ " anim=" + a + " transit=" + appTransitionToString(transit)
+ " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
} else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
- a = createRelaunchAnimation(appWidth, appHeight, containingFrame, contentInsets);
+ a = createRelaunchAnimation(frame, insets);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=" + mNextAppTransition
@@ -1117,14 +1167,14 @@
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = createClipRevealAnimationLocked(transit, enter, appFrame);
+ a = createClipRevealAnimationLocked(transit, enter, frame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
- a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+ a = createScaleUpAnimationLocked(transit, enter, frame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
@@ -1135,7 +1185,7 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
- appWidth, appHeight, transit, taskId);
+ frame, transit, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1149,8 +1199,8 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
a = createAspectScaledThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
- containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
+ getThumbnailTransitionState(enter), orientation, transit, frame,
+ insets, surfaceInsets, freeform, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
@@ -1467,15 +1517,15 @@
pw.print(Integer.toHexString(mNextAppTransitionInPlace));
break;
case NEXT_TRANSIT_TYPE_SCALE_UP: {
- getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ getDefaultNextAppTransitionStartRect(mTmpRect);
pw.print(prefix); pw.print("mNextAppTransitionStartX=");
- pw.print(mTmpStartRect.left);
+ pw.print(mTmpRect.left);
pw.print(" mNextAppTransitionStartY=");
- pw.println(mTmpStartRect.top);
+ pw.println(mTmpRect.top);
pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
- pw.print(mTmpStartRect.width());
+ pw.print(mTmpRect.width());
pw.print(" mNextAppTransitionStartHeight=");
- pw.println(mTmpStartRect.height());
+ pw.println(mTmpRect.height());
break;
}
case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index bf7063f..a61e83f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -300,7 +300,8 @@
return false;
}
- if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+ if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface
+ || animating || mAppToken.startingDisplayed)
&& animation != null) {
if (!animating) {
if (DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5a1ed58..c5bd3a7 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -17,6 +17,9 @@
package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerService.TAG;
import com.android.server.input.InputApplicationHandle;
import com.android.server.wm.WindowManagerService.H;
@@ -52,6 +55,13 @@
final boolean voiceInteraction;
+ // Whether the window has a saved surface from last pause, which can be
+ // used to start an entering animation earlier.
+ boolean mHasSavedSurface;
+
+ // Whether we're performing an entering animation with a saved surface.
+ boolean mAnimatingWithSavedSurface;
+
Task mTask;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -114,11 +124,14 @@
// This application will have its window replaced due to relaunch. This allows window manager
// to differentiate between simple removal of a window and replacement. In the latter case it
// will preserve the old window until the new one is drawn.
- boolean mReplacingWindow;
+ boolean mWillReplaceWindow;
// If true, the replaced window was already requested to be removed.
boolean mReplacingRemoveRequested;
// Whether the replacement of the window should trigger app transition animation.
boolean mAnimateReplacingWindow;
+ // If not null, the window that will be used to replace the old one. This is being set when
+ // the window is added and unset when this window reports its first draw.
+ WindowState mReplacingWindow;
AppWindowToken(WindowManagerService _service, IApplicationToken _token,
boolean _voiceInteraction) {
@@ -257,10 +270,13 @@
final int N = allAppWindows.size();
for (int i=0; i<N; i++) {
WindowState win = allAppWindows.get(i);
+ // If we're animating with a saved surface, we're already visible.
+ // Return true so that the alpha doesn't get cleared.
if (!win.mAppFreezing
- && (win.mViewVisibility == View.VISIBLE ||
- (win.mWinAnimator.isAnimating() &&
- !service.mAppTransition.isTransitionSet()))
+ && (win.mViewVisibility == View.VISIBLE
+ || mAnimatingWithSavedSurface
+ || (win.mWinAnimator.isAnimating() &&
+ !service.mAppTransition.isTransitionSet()))
&& !win.mDestroying && win.isDrawnLw()) {
return true;
}
@@ -283,6 +299,52 @@
}
}
+ /**
+ * Checks whether we should save surfaces for this app.
+ *
+ * @return true if the surfaces should be saved, false otherwise.
+ */
+ boolean shouldSaveSurface() {
+ // We want to save surface if the app's windows are "allDrawn", or if we're
+ // currently animating with save surfaces. (If the app didn't even finish
+ // drawing when the user exits, but we have a saved surface from last time,
+ // we still want to keep that surface.)
+ mHasSavedSurface = allDrawn || mAnimatingWithSavedSurface;
+ if (mHasSavedSurface) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "Saving surface: " + this);
+ return true;
+ }
+ return false;
+ }
+
+ void restoreSavedSurfaces() {
+ if (!mHasSavedSurface) {
+ return;
+ }
+
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "Restoring saved surfaces: " + this + ", allDrawn=" + allDrawn);
+
+ mHasSavedSurface = false;
+ mAnimatingWithSavedSurface = true;
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState ws = windows.get(i);
+ ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+ }
+ }
+
+ void destroySavedSurfaces() {
+ if (mHasSavedSurface) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "Destroying saved surface: " + this);
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ final WindowState win = windows.get(i);
+ win.mWinAnimator.destroySurfaceLocked();
+ }
+ }
+ }
+
@Override
void removeAllWindows() {
for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 39479c1..fab8ee5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -18,7 +18,7 @@
import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.app.ActivityManager.HOME_STACK_ID;
-
+import static android.app.ActivityManager.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -243,10 +243,30 @@
}
void moveStack(TaskStack stack, boolean toTop) {
+ if (stack.mStackId == PINNED_STACK_ID && !toTop) {
+ // Pinned stack is always-on-top silly...
+ Slog.w(TAG, "Ignoring move of always-on-top stack=" + stack + " to bottom");
+ return;
+ }
+
if (!mStacks.remove(stack)) {
Slog.wtf(TAG, "moving stack that was not added: " + stack, new Throwable());
}
- mStacks.add(toTop ? mStacks.size() : 0, stack);
+
+ int addIndex = toTop ? mStacks.size() : 0;
+
+ if (toTop
+ && mService.isStackVisibleLocked(PINNED_STACK_ID)
+ && stack.mStackId != PINNED_STACK_ID) {
+ // The pinned stack is always the top most stack (always-on-top) when it is visible.
+ // So, stack is moved just below the pinned stack.
+ addIndex--;
+ TaskStack topStack = mStacks.get(addIndex);
+ if (topStack.mStackId != PINNED_STACK_ID) {
+ throw new IllegalStateException("Pinned stack isn't top stack??? " + mStacks);
+ }
+ }
+ mStacks.add(addIndex, stack);
}
void detachStack(TaskStack stack) {
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 2d87123..04cba81 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,6 +18,8 @@
import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -87,6 +89,8 @@
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? mDividerWidth : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : mDividerWidth;
+ view.setPointerShape(
+ landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
width, height, TYPE_DOCK_DIVIDER,
FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 6c391ad..5f911c3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerService.DEBUG_INPUT;
import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
import android.app.ActivityManagerNative;
import android.graphics.Rect;
@@ -179,7 +181,17 @@
if (modal && child.mAppToken != null) {
// Limit the outer touch to the activity stack region.
flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+ // If this is a modal window we need to dismiss it if it's not full screen and the touch
+ // happens outside of the frame that displays the content. This means we need to
+ // intercept touches outside of that window. The dim layer user associated with the
+ // window (task or stack) will give us the good bounds, as they would be used to display
+ // the dim layer.
+ final DimLayer.DimLayerUser dimLayerUser = child.getDimLayerUser();
+ if (dimLayerUser != null) {
+ dimLayerUser.getBounds(mTmpRect);
+ } else {
+ child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH);
+ }
inputWindowHandle.touchableRegion.set(mTmpRect);
} else {
// Not modal or full screen modal
@@ -227,7 +239,9 @@
inputWindowHandle.scaleFactor = 1;
}
-
+ if (DEBUG_INPUT) {
+ Slog.d(WindowManagerService.TAG, "addInputWindowHandle: " + inputWindowHandle);
+ }
addInputWindowHandleLw(inputWindowHandle);
}
@@ -428,7 +442,7 @@
* Layer assignment is assumed to be complete by the time this is called.
*/
public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
- if (WindowManagerService.DEBUG_FOCUS_LIGHT || WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) {
Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow);
}
@@ -464,7 +478,7 @@
public void pauseDispatchingLw(WindowToken window) {
if (! window.paused) {
- if (WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_INPUT) {
Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
}
@@ -475,7 +489,7 @@
public void resumeDispatchingLw(WindowToken window) {
if (window.paused) {
- if (WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_INPUT) {
Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
}
@@ -486,7 +500,7 @@
public void freezeInputDispatchingLw() {
if (! mInputDispatchFrozen) {
- if (WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_INPUT) {
Slog.v(WindowManagerService.TAG, "Freezing input dispatching");
}
@@ -497,7 +511,7 @@
public void thawInputDispatchingLw() {
if (mInputDispatchFrozen) {
- if (WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_INPUT) {
Slog.v(WindowManagerService.TAG, "Thawing input dispatching");
}
@@ -508,7 +522,7 @@
public void setEventDispatchingLw(boolean enabled) {
if (mInputDispatchEnabled != enabled) {
- if (WindowManagerService.DEBUG_INPUT) {
+ if (DEBUG_INPUT) {
Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6527881..ad44196 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.HOME_STACK_ID;
import static android.app.ActivityManager.PINNED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static com.android.server.wm.WindowManagerService.TAG;
@@ -324,6 +325,10 @@
return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
}
+ boolean inHomeStack() {
+ return mStack != null && mStack.mStackId == HOME_STACK_ID;
+ }
+
boolean inFreeformWorkspace() {
return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
}
@@ -337,6 +342,11 @@
return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
}
+ AppWindowToken getTopAppWindowToken() {
+ final int tokensCount = mAppTokens.size();
+ return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null;
+ }
+
@Override
public boolean isFullscreen() {
if (useCurrentBounds()) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9b3d478..df664bd 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -409,7 +409,7 @@
final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode
== DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
- mDisplayContent.mDividerControllerLocked.getWidthAdjustment(),
+ mDisplayContent.mDividerControllerLocked.getWidth(),
dockedOnTopOrLeft);
}
@@ -459,7 +459,7 @@
dockedStack.getRawBounds(mTmpRect2);
final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2,
- mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft);
+ mDisplayContent.mDividerControllerLocked.getWidth(), dockedOnTopOrLeft);
}
@@ -469,13 +469,13 @@
* @param outBounds Output bounds that should be used for the stack.
* @param stackId Id of stack we are calculating the bounds for.
* @param dockedBounds Bounds of the docked stack.
- * @param adjustment Additional adjustment to make to the output bounds close to the side of the
- * dock.
- * @param dockOntopOrLeft If the docked stack is on the top or left side of the screen.
+ * @param dockDividerWidth We need to know the width of the divider make to the output bounds
+ * close to the side of the dock.
+ * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
*/
private static void getStackDockedModeBounds(
- Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment,
- boolean dockOntopOrLeft) {
+ Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+ boolean dockOnTopOrLeft) {
final boolean dockedStack = stackId == DOCKED_STACK_ID;
final boolean splitHorizontally = displayRect.width() > displayRect.height();
@@ -484,34 +484,34 @@
// The initial bounds of the docked stack when it is created half the screen space and
// its bounds can be adjusted after that. The bounds of all other stacks are adjusted
// to occupy whatever screen space the docked stack isn't occupying.
- if (dockOntopOrLeft) {
+ if (dockOnTopOrLeft) {
if (splitHorizontally) {
- outBounds.right = displayRect.centerX() - adjustment;
+ outBounds.right = displayRect.centerX() - dockDividerWidth / 2;
} else {
- outBounds.bottom = displayRect.centerY() - adjustment;
+ outBounds.bottom = displayRect.centerY() - dockDividerWidth / 2;
}
} else {
if (splitHorizontally) {
- outBounds.left = displayRect.centerX() + adjustment;
+ outBounds.left = displayRect.centerX() + dockDividerWidth / 2;
} else {
- outBounds.top = displayRect.centerY() + adjustment;
+ outBounds.top = displayRect.centerY() + dockDividerWidth / 2;
}
}
return;
}
// Other stacks occupy whatever space is left by the docked stack.
- if (!dockOntopOrLeft) {
+ if (!dockOnTopOrLeft) {
if (splitHorizontally) {
- outBounds.right = dockedBounds.left - adjustment;
+ outBounds.right = dockedBounds.left - dockDividerWidth;
} else {
- outBounds.bottom = dockedBounds.top - adjustment;
+ outBounds.bottom = dockedBounds.top - dockDividerWidth;
}
} else {
if (splitHorizontally) {
- outBounds.left = dockedBounds.right + adjustment;
+ outBounds.left = dockedBounds.right + dockDividerWidth;
} else {
- outBounds.top = dockedBounds.bottom + adjustment;
+ outBounds.top = dockedBounds.bottom + dockDividerWidth;
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 928a117..f4a4140 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -413,7 +414,7 @@
final AppWindowToken atoken = win.mAppToken;
if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
- if (atoken == null || atoken.allDrawn) {
+ if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) {
if (winAnimator.performShowLocked()) {
setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
@@ -731,8 +732,15 @@
mWindowPlacerLocked.requestTraversal();
}
+ if (mAnimating && !wasAnimating && Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+ }
+
if (!mAnimating && wasAnimating) {
mWindowPlacerLocked.requestTraversal();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+ }
}
mService.destroyPreservedSurfaceLocked();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 643c471..140fbaf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1327,8 +1327,14 @@
addAttachedWindowToListLocked(win, addToToken);
}
- if (win.mAppToken != null && addToToken) {
- win.mAppToken.allAppWindows.add(win);
+ final AppWindowToken appToken = win.mAppToken;
+ if (appToken != null) {
+ if (addToToken) {
+ appToken.allAppWindows.add(win);
+ }
+ if (appToken.mWillReplaceWindow) {
+ appToken.mReplacingWindow = win;
+ }
}
}
@@ -2053,7 +2059,7 @@
}
private void prepareWindowReplacementTransition(AppWindowToken atoken) {
- if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) {
+ if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
return;
}
atoken.allDrawn = false;
@@ -2157,6 +2163,8 @@
+ " isAnimating=" + win.mWinAnimator.isAnimating()
+ " app-animation="
+ (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+ + " mWillReplaceWindow="
+ + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
+ " inPendingTransaction="
+ (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
+ " mDisplayFrozen=" + mDisplayFrozen);
@@ -2168,8 +2176,8 @@
// animation wouldn't be seen.
if (win.mHasSurface && okToDisplay()) {
final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mReplacingWindow) {
- // This window is going to be replaced. We need to kepp it around until the new one
+ if (appToken != null && appToken.mWillReplaceWindow) {
+ // This window is going to be replaced. We need to keep it around until the new one
// gets added, then we will get rid of this one.
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is "
+ "added");
@@ -2629,6 +2637,11 @@
}
}
dragResizing = win.isDragResizing();
+ if (win.isAnimatingWithSavedSurface()) {
+ // If we're animating with a saved surface now, request client to report draw.
+ // We still need to know when the real thing is drawn.
+ toBeDisplayed = true;
+ }
try {
if (!win.mHasSurface) {
surfaceChanged = true;
@@ -2679,9 +2692,14 @@
if (winAnimator.mSurfaceControl != null) {
if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
+ ": mExiting=" + win.mExiting);
+ // If we are using a saved surface to do enter animation, just let the
+ // animation run and don't destroy the surface. This could happen when
+ // the app sets visibility to invisible for the first time after resume,
+ // or when the user exits immediately after a resume. In both cases, we
+ // don't want to destroy the saved surface.
// If we are not currently running the exit animation, we
// need to see about starting one.
- if (!win.mExiting) {
+ if (!win.mExiting && !win.isAnimatingWithSavedSurface()) {
surfaceChanged = true;
// Try starting an animation; if there isn't one, we
// can destroy the surface right away.
@@ -2707,7 +2725,9 @@
if (mInputMethodWindow == win) {
mInputMethodWindow = null;
}
- winAnimator.destroySurfaceLocked();
+ if (!win.shouldSaveSurface()) {
+ winAnimator.destroySurfaceLocked();
+ }
}
//TODO (multidisplay): Magnification is supported only for the default
if (mAccessibilityController != null
@@ -2833,7 +2853,8 @@
try {
synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
- if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win);
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState="
+ + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
getDefaultDisplayContentLocked().pendingLayoutChanges |=
@@ -2862,10 +2883,9 @@
"applyAnimation: atoken=" + atoken);
// Determine the visible rect to calculate the thumbnail clip
- WindowState win = atoken.findMainWindow();
- Rect containingFrame = new Rect(0, 0, width, height);
- Rect contentInsets = new Rect();
- Rect appFrame = new Rect(0, 0, width, height);
+ final WindowState win = atoken.findMainWindow();
+ final Rect frame = new Rect(0, 0, width, height);
+ final Rect insets = new Rect();
Rect surfaceInsets = null;
final boolean fullscreen = win != null && win.isFullscreen(width, height);
final boolean freeform = win != null && win.inFreeformWorkspace();
@@ -2876,25 +2896,19 @@
// won't exactly match the final freeform window frame (e.g. when overlapping with
// the status bar). In that case we need to use the final frame.
if (freeform) {
- containingFrame.set(win.mFrame);
+ frame.set(win.mFrame);
} else {
- containingFrame.set(win.mContainingFrame);
+ frame.set(win.mContainingFrame);
}
surfaceInsets = win.getAttrs().surfaceInsets;
if (fullscreen || docked) {
// For fullscreen windows use the window frames and insets to set the thumbnail
// clip. For non-fullscreen windows we use the app display region so the clip
- // isn't affected by the window insets. Docked windows are cropped to the system
- // decorations, so we need tell the animation about it too.
- contentInsets.set(win.mContentInsets);
- appFrame.set(win.mFrame);
- } else {
- appFrame.set(containingFrame);
+ // isn't affected by the window insets.
+ insets.set(win.mContentInsets);
}
}
- final int containingWidth = containingFrame.width();
- final int containingHeight = containingFrame.height();
if (atoken.mLaunchTaskBehind) {
// Differentiate the two animations. This one which is briefly on the screen
// gets the !enter animation, and the other activity which remains on the
@@ -2902,17 +2916,11 @@
enter = false;
}
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition."
- + " transit=" + AppTransition.appTransitionToString(transit)
- + " enter=" + enter
- + " containingWidth=" + containingWidth
- + " containingHeight=" + containingHeight
- + " containingFrame=" + containingFrame
- + " contentInsets=" + contentInsets
- + " surfaceInsets=" + surfaceInsets
- + " appFrame=" + appFrame);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
- containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
- surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+ + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+ mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
+ freeform, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -2922,6 +2930,8 @@
}
Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
}
+ final int containingWidth = frame.width();
+ final int containingHeight = frame.height();
atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
mAppTransition.canSkipFirstFrame());
}
@@ -3273,10 +3283,8 @@
}
}
- final TaskStack dockedStack = mStackIdToStack.get(DOCKED_STACK_ID);
- final TaskStack freeformStack = mStackIdToStack.get(FREEFORM_WORKSPACE_STACK_ID);
- if ((dockedStack != null && dockedStack.isVisibleLocked())
- || (freeformStack != null && freeformStack.isVisibleLocked())) {
+ if (isStackVisibleLocked(DOCKED_STACK_ID)
+ || isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
// We don't let app affect the system orientation when in freeform or docked mode since
// they don't occupy the entire display and their request can conflict with other apps.
return SCREEN_ORIENTATION_UNSPECIFIED;
@@ -3920,7 +3928,7 @@
// transition animation
// * or this is an opening app and windows are being replaced.
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
- (visible && wtoken.mReplacingWindow)) {
+ (visible && wtoken.mWillReplaceWindow)) {
boolean changed = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -4507,6 +4515,11 @@
}
}
+ boolean isStackVisibleLocked(int stackId) {
+ final TaskStack stack = mStackIdToStack.get(stackId);
+ return (stack != null && stack.isVisibleLocked());
+ }
+
public void setDockedStackCreateMode(int mode) {
synchronized (mWindowMap) {
sDockedStackCreateMode = mode;
@@ -4668,7 +4681,7 @@
throw new IllegalArgumentException("resizeStack: stackId " + stackId
+ " not found.");
}
- if (stack.setBounds(bounds, configs, taskBounds)) {
+ if (stack.setBounds(bounds, configs, taskBounds) && stack.isVisibleLocked()) {
stack.resizeWindows();
stack.getDisplayContent().layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
@@ -4721,6 +4734,26 @@
}
}
+ /**
+ * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+ * performance, only one layout pass should be done. This can be called multiple times, and
+ * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
+ */
+ public void deferSurfaceLayout() {
+ synchronized (mWindowMap) {
+ mWindowPlacerLocked.deferLayout();
+ }
+ }
+
+ /**
+ * Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()}
+ */
+ public void continueSurfaceLayout() {
+ synchronized (mWindowMap) {
+ mWindowPlacerLocked.continueLayout();
+ }
+ }
+
public void getTaskBounds(int taskId, Rect bounds) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -5407,8 +5440,8 @@
if (visible) {
// TODO(multi-display): support multiple displays
if (mCircularDisplayMask == null) {
- int screenOffset = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.circular_display_mask_offset);
+ int screenOffset = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_windowOutsetBottom);
int maskThickness = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.circular_display_mask_thickness);
@@ -6081,6 +6114,10 @@
final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState w = windows.get(i);
+ // Discard surface after orientation change, these can't be reused.
+ if (w.mAppToken != null) {
+ w.mAppToken.destroySavedSurfaces();
+ }
if (w.mHasSurface) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w);
w.mOrientationChanging = true;
@@ -8358,7 +8395,7 @@
final int numTokens = tokens.size();
for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mIsExiting && !wtoken.mReplacingWindow) {
+ if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
continue;
}
i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8428,7 +8465,8 @@
} else if (wtoken != null) {
winAnimator.mAnimLayer =
w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
- if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) {
+ if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
+ wtoken.mReplacingWindow != w) {
// We know that we will be animating a relaunching window in the near future,
// which will receive a z-order increase. We want the replaced window to
// immediately receive the same treatment, e.g. to be above the dock divider.
@@ -8735,12 +8773,14 @@
} else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
+ ws + " surface=" + wsa.mSurfaceControl
- + " token=" + ws.mAppToken);
+ + " token=" + ws.mAppToken
+ + " saved=" + ws.mAppToken.mHasSavedSurface);
if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
wsa.mSurfaceControl.destroy();
wsa.mSurfaceShown = false;
wsa.mSurfaceControl = null;
ws.mHasSurface = false;
+ ws.mAppToken.mHasSavedSurface = false;
leakedSurface = true;
}
}
@@ -9974,7 +10014,9 @@
Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token);
return;
}
- appWindowToken.mReplacingWindow = true;
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken
+ + " as replacing window.");
+ appWindowToken.mWillReplaceWindow = true;
appWindowToken.mAnimateReplacingWindow = animate;
}
}
@@ -10180,8 +10222,7 @@
@Override
public boolean isStackVisible(int stackId) {
synchronized (mWindowMap) {
- final TaskStack stack = mStackIdToStack.get(stackId);
- return (stack != null && stack.isVisibleLocked());
+ return WindowManagerService.this.isStackVisibleLocked(stackId);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7ea64f1..1d2cb75 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,6 +28,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
@@ -35,6 +38,7 @@
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.graphics.Point;
import android.os.PowerManager;
@@ -55,7 +59,6 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
@@ -446,7 +449,8 @@
+ WindowManagerService.TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
mAttachedWindow = attachedWindow;
- if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
+ if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to "
+ + mAttachedWindow);
final WindowList childWindows = mAttachedWindow.mChildWindows;
final int numChildWindows = childWindows.size();
@@ -552,7 +556,7 @@
@Override
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
Rect osf) {
- if (mAppToken != null && mAppToken.mReplacingWindow
+ if (mAppToken != null && mAppToken.mWillReplaceWindow
&& (mExiting || !mAppToken.mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
@@ -1307,10 +1311,12 @@
void maybeRemoveReplacedWindow() {
AppWindowToken token = mAppToken;
- if (token != null && token.mReplacingWindow) {
- token.mReplacingWindow = false;
+ if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) {
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
+ token.mWillReplaceWindow = false;
token.mAnimateReplacingWindow = false;
token.mReplacingRemoveRequested = false;
+ token.mReplacingWindow = null;
for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
WindowState win = token.allAppWindows.get(i);
if (win.mExiting) {
@@ -1501,6 +1507,28 @@
return mExiting || (mService.mClosingApps.contains(mAppToken));
}
+ boolean isAnimatingWithSavedSurface() {
+ return mAppToken != null && mAppToken.mAnimatingWithSavedSurface;
+ }
+
+ boolean shouldSaveSurface() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ // Don't save surfaces on Svelte devices.
+ return false;
+ }
+
+ Task task = getTask();
+ if (task == null || task.inHomeStack()
+ || task.getTopAppWindowToken() != mAppToken) {
+ // Don't save surfaces for home stack apps. These usually resume and draw
+ // first frame very fast. Saving surfaces are mostly a waste of memory.
+ // Don't save if the window is not the topmost window.
+ return false;
+ }
+
+ return mAppToken.shouldSaveSurface();
+ }
+
@Override
public boolean isDefaultDisplay() {
final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1754123..80f1094 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -38,7 +38,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.Debug;
import android.os.RemoteException;
@@ -526,6 +525,12 @@
Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState="
+ drawStateToString());
}
+ if (mWin.mAppToken != null) {
+ // App has drawn something to its windows, we're no longer animating with
+ // the saved surfaces. If the user exits now, we only want to save again
+ // if allDrawn is true.
+ mWin.mAppToken.mAnimatingWithSavedSurface = false;
+ }
if (mDrawState == DRAW_PENDING) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
@@ -536,6 +541,7 @@
mDrawState = COMMIT_DRAW_PENDING;
return true;
}
+
return false;
}
@@ -555,7 +561,8 @@
mDrawState = READY_TO_SHOW;
boolean result = false;
final AppWindowToken atoken = mWin.mAppToken;
- if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface ||
+ mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
if (mDestroyPreservedSurfaceUponRedraw && result) {
@@ -996,12 +1003,16 @@
}
void destroySurfaceLocked() {
- if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
- mWin.mAppToken.startingDisplayed = false;
+ final AppWindowToken wtoken = mWin.mAppToken;
+ if (wtoken != null) {
+ wtoken.mHasSavedSurface = false;
+ wtoken.mAnimatingWithSavedSurface = false;
+ if (mWin == wtoken.startingWindow) {
+ wtoken.startingDisplayed = false;
+ }
}
if (mSurfaceControl != null) {
-
int i = mWin.mChildWindows.size();
while (i > 0) {
i--;
@@ -1428,7 +1439,7 @@
// living in a different stack. If we suddenly crop it to the new stack bounds, it might
// get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
// gets removed. The window that will replace it will abide them.
- if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) {
+ if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
TaskStack stack = w.getTask().mStack;
stack.getBounds(mTmpStackBounds);
// When we resize we use the big surface approach, which means we can't trust the
@@ -1854,8 +1865,7 @@
}
}
- if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
- && mWin.mAppToken != null) {
+ if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
mWin.mAppToken.firstWindowDrawn = true;
if (mWin.mAppToken.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 0c004b2..aca0f5b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,6 +17,7 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerService.DEBUG;
import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -111,13 +112,34 @@
private int mPreferredModeId = 0;
private boolean mTraversalScheduled;
+ private int mDeferDepth = 0;
public WindowSurfacePlacer(WindowManagerService service) {
mService = service;
mWallpaperControllerLocked = mService.mWallpaperControllerLocked;
}
+ /**
+ * See {@link WindowManagerService#deferSurfaceLayout()}
+ */
+ void deferLayout() {
+ mDeferDepth++;
+ }
+
+ /**
+ * See {@link WindowManagerService#continueSurfaceLayout()}
+ */
+ void continueLayout() {
+ mDeferDepth--;
+ if (mDeferDepth <= 0) {
+ performSurfacePlacement();
+ }
+ }
+
final void performSurfacePlacement() {
+ if (mDeferDepth > 0) {
+ return;
+ }
int loopCount = 6;
do {
mTraversalScheduled = false;
@@ -340,6 +362,10 @@
// Don't remove this window until rotation has completed.
continue;
}
+ // Discard the saved surface if window size is changed, it can't be reused.
+ if (win.mAppToken != null) {
+ win.mAppToken.destroySavedSurfaces();
+ }
win.reportResized();
mService.mResizingWindows.remove(i);
}
@@ -371,7 +397,9 @@
if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
wallpaperDestroyed = true;
}
- win.mWinAnimator.destroySurfaceLocked();
+ if (!win.shouldSaveSurface()) {
+ win.mWinAnimator.destroySurfaceLocked();
+ }
} while (i > 0);
mService.mDestroySurface.clear();
}
@@ -1163,7 +1191,10 @@
if (animLp != null) {
int layer = -1;
for (int j = 0; j < wtoken.windows.size(); j++) {
- WindowState win = wtoken.windows.get(j);
+ final WindowState win = wtoken.windows.get(j);
+ // Clearing the mExiting flag before entering animation. It will be set
+ // to true if app window is removed, or window relayout to invisible.
+ win.mExiting = false;
if (win.mWinAnimator.mAnimLayer > layer) {
layer = win.mWinAnimator.mAnimLayer;
}
@@ -1174,6 +1205,8 @@
}
}
createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+
+ wtoken.restoreSavedSurfaces();
}
AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null :
@@ -1219,6 +1252,10 @@
+ wtoken.allDrawn + " startingDisplayed="
+ wtoken.startingDisplayed + " startingMoved="
+ wtoken.startingMoved);
+
+ if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) {
+ continue;
+ }
if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
return false;
}
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 89b2a47..01acdef 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -414,12 +414,9 @@
return NO_ERROR;
}
if (Surface::isValid(connection.mSurface)) {
- connection.mSurface.clear();
- }
- if (connection.mSurface != NULL) {
connection.mSurface->setSidebandStream(NULL);
- connection.mSurface.clear();
}
+ connection.mSurface.clear();
if (connection.mThread != NULL) {
connection.mThread->shutdown();
connection.mThread.clear();
@@ -616,6 +613,9 @@
return BAD_VALUE;
}
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
+ if (!Surface::isValid(surface)) {
+ return BAD_VALUE;
+ }
return tvInputHal->addOrUpdateStream(deviceId, streamId, surface);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8385685..b4c8f96 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.devicepolicy;
+import com.google.android.collect.Sets;
+
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
@@ -88,6 +90,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
@@ -183,8 +186,6 @@
private static final int MONITORING_CERT_NOTIFICATION_ID = R.string.ssl_ca_cert_warning;
private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
- private static final boolean DBG = false;
-
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
private static final String ATTR_SETUP_COMPLETE = "setup-complete";
private static final String ATTR_PERMISSION_POLICY = "permission-policy";
@@ -274,6 +275,7 @@
final Injector mInjector;
final IPackageManager mIPackageManager;
final UserManager mUserManager;
+ final UserManagerInternal mUserManagerInternal;
final LocalService mLocalService;
@@ -357,8 +359,10 @@
getSendingUserId());
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
|| ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
- if (DBG) Slog.v(LOG_TAG, "Sending password expiration notifications for action "
- + action + " for user " + userHandle);
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, "Sending password expiration notifications for action "
+ + action + " for user " + userHandle);
+ }
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -1014,7 +1018,7 @@
private void handlePackagesChanged(String packageName, int userHandle) {
boolean removed = false;
- if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
+ if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
DevicePolicyData policy = getUserData(userHandle);
synchronized (this) {
for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -1079,6 +1083,10 @@
return UserManager.get(mContext);
}
+ UserManagerInternal getUserManagerInternal() {
+ return LocalServices.getService(UserManagerInternal.class);
+ }
+
NotificationManager getNotificationManager() {
return mContext.getSystemService(NotificationManager.class);
}
@@ -1233,6 +1241,7 @@
mOwners = Preconditions.checkNotNull(injector.newOwners());
mUserManager = Preconditions.checkNotNull(injector.getUserManager());
+ mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal());
mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
mLocalService = new LocalService();
@@ -1327,10 +1336,13 @@
synchronized (this) {
mOwners.load();
findOwnerComponentIfNecessaryLocked();
+ migrateUserRestrictionsIfNecessaryLocked();
// TODO PO may not have a class name either due to b/17652534. Address that too.
updateDeviceOwnerLocked();
+
+ // TODO Notify UM to update restrictions (?)
}
}
@@ -1350,14 +1362,113 @@
if (doComponent == null) {
Slog.e(LOG_TAG, "Device-owner isn't registered as device-admin");
} else {
- mOwners.setDeviceOwner(
+ mOwners.setDeviceOwnerWithRestrictionsMigrated(
doComponent,
mOwners.getDeviceOwnerName(),
- mOwners.getDeviceOwnerUserId());
+ mOwners.getDeviceOwnerUserId(),
+ !mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
mOwners.writeDeviceOwner();
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Device owner component filled in");
+ }
}
}
+ /**
+ * We didn't use to persist user restrictions for each owners but only persisted in user
+ * manager.
+ */
+ private void migrateUserRestrictionsIfNecessaryLocked() {
+ boolean migrated = false;
+ // Migrate for the DO. Basically all restrictions should be considered to be set by DO,
+ // except for the "system controlled" ones.
+ if (mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Migrating DO user restrictions");
+ }
+ migrated = true;
+
+ // Migrate user 0 restrictions to DO, except for "system" restrictions.
+ final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
+
+ migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
+ /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+ mOwners.setDeviceOwnerUserRestrictionsMigrated();
+ }
+
+ // Migrate for POs. We have a few more exceptions.
+ final Set<String> normalExceptionList = Sets.newArraySet(
+ UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_SMS);
+ normalExceptionList.addAll(UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+
+ final Set<String> managedExceptionList = new ArraySet<>(normalExceptionList.size() + 1);
+ managedExceptionList.addAll(normalExceptionList);
+ managedExceptionList.add(UserManager.DISALLOW_WALLPAPER);
+
+ for (UserInfo ui : mUserManager.getUsers()) {
+ final int userId = ui.id;
+ if (mOwners.getProfileOwnerUserRestrictionsNeedsMigration(userId)) {
+ if (userId != UserHandle.USER_SYSTEM) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Migrating PO user restrictions for user " + userId);
+ }
+ migrated = true;
+
+ final ActiveAdmin profileOwnerAdmin = getProfileOwnerAdminLocked(userId);
+
+ final Set<String> exceptionList =
+ ui.isManagedProfile() ? managedExceptionList : normalExceptionList;
+
+ migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
+ exceptionList);
+ }
+
+ mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
+ }
+ }
+ if (VERBOSE_LOG && migrated) {
+ Log.v(LOG_TAG, "User restrictions migrated.");
+ }
+ }
+
+ private void migrateUserRestrictionsForUser(UserHandle user, ActiveAdmin admin,
+ Set<String> exceptionList) {
+ final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions(
+ user.getIdentifier());
+
+ final Bundle newSystemRestrictions = new Bundle();
+ final Bundle newOwnerRestrictions = new Bundle();
+
+ for (String key : origRestrictions.keySet()) {
+ if (!origRestrictions.getBoolean(key)) {
+ continue;
+ }
+ if (exceptionList.contains(key)) {
+ newSystemRestrictions.putBoolean(key, true);
+ } else {
+ newOwnerRestrictions.putBoolean(key, true);
+ }
+ }
+
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
+ Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions);
+ Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
+ }
+ mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
+ newSystemRestrictions);
+
+ if (admin != null) {
+ admin.ensureUserRestrictions().clear();
+ admin.ensureUserRestrictions().putAll(newOwnerRestrictions);
+ } else {
+ Slog.w(LOG_TAG, "ActiveAdmin for DO/PO not found. user=" + user.getIdentifier());
+ }
+ saveSettingsLocked(user.getIdentifier());
+ }
+
private ComponentName findAdminComponentWithPackageLocked(String packageName, int userId) {
final DevicePolicyData policy = getUserData(userId);
final int n = policy.mAdminList.size();
@@ -1373,7 +1484,7 @@
nFound++;
}
}
- if (nFound > 0) {
+ if (nFound > 1) {
Slog.w(LOG_TAG, "Multiple DA found; assume the first one is DO.");
}
return found;
@@ -1636,6 +1747,9 @@
? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
: new File(mInjector.environmentGetUserSystemDirectory(userHandle),
DEVICE_POLICIES_XML).getAbsolutePath();
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Opening " + base);
+ }
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
@@ -1808,7 +1922,8 @@
try {
DeviceAdminInfo dai = findAdmin(
ComponentName.unflattenFromString(name), userHandle);
- if (DBG && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
+ if (VERBOSE_LOG
+ && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
!= userHandle)) {
Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
+ dai.getActivityInfo().applicationInfo.uid + " for user "
@@ -1988,8 +2103,10 @@
long token = mInjector.binderClearCallingIdentity();
try {
String value = cameraDisabled ? "1" : "0";
- if (DBG) Slog.v(LOG_TAG, "Change in camera state ["
- + cameraPropertyForUser + "] = " + value);
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, "Change in camera state ["
+ + cameraPropertyForUser + "] = " + value);
+ }
mInjector.systemPropertiesSet(cameraPropertyForUser, value);
} finally {
mInjector.binderRestoreCallingIdentity(token);
@@ -4513,8 +4630,10 @@
}
UserHandle callingUser = mInjector.binderGetCallingUserHandle();
// Check if this is the profile owner who is calling
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final ActiveAdmin admin =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
synchronized (this) {
+ admin.userRestrictions = null;
clearUserPoliciesLocked(callingUser);
final int userId = callingUser.getIdentifier();
mOwners.removeProfileOwner(userId);
@@ -4533,38 +4652,19 @@
final long ident = mInjector.binderClearCallingIdentity();
try {
- clearUserRestrictions(userHandle);
mIPackageManager.updatePermissionFlagsForAllApps(
PackageManager.FLAG_PERMISSION_POLICY_FIXED,
0 /* flagValues */, userHandle.getIdentifier());
+ // TODO This will not revert audio mute restrictions if they were set. b/24981972
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+ }
} catch (RemoteException re) {
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
}
-
- private void clearUserRestrictions(UserHandle userHandle) {
- Bundle userRestrictions = mUserManager.getUserRestrictions();
- mUserManager.setUserRestrictions(new Bundle(), userHandle);
- if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
- try {
- mInjector.getIAudioService().setMasterMute(true, 0, mContext.getPackageName(),
- userHandle.getIdentifier());
- } catch (RemoteException e) {
- // Not much we can do here.
- }
- }
- if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
- try {
- mInjector.getIAudioService().setMicrophoneMute(true, mContext.getPackageName(),
- userHandle.getIdentifier());
- } catch (RemoteException e) {
- // Not much we can do here.
- }
- }
- }
-
@Override
public boolean hasUserSetupCompleted() {
return hasUserSetupCompleted(UserHandle.getCallingUserId());
@@ -5503,95 +5603,123 @@
}
@Override
- public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+ public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
final UserHandle user = new UserHandle(userHandle);
- synchronized (this) {
- ActiveAdmin activeAdmin =
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- boolean isDeviceOwner = isDeviceOwner(who);
- if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
- && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
- throw new SecurityException("Profile owners cannot set user restriction " + key);
- }
- if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
- throw new SecurityException("User restriction " + key + " cannot be changed");
- }
- boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (this) {
+ ActiveAdmin activeAdmin =
+ getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ boolean isDeviceOwner = isDeviceOwner(who);
+ if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
+ && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
+ throw new SecurityException(
+ "Profile owners cannot set user restriction " + key);
+ }
+ if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
+ throw new SecurityException("User restriction " + key + " cannot be changed");
+ }
- long id = mInjector.binderClearCallingIdentity();
- try {
- if (enabled && !alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
- userHandle);
- } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF,
- userHandle);
- mInjector.settingsSecurePutStringForUser(
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
- userHandle);
- } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
- // Only disable adb if changing for system user, since it is global
- // TODO: should this be admin user?
- if (userHandle == UserHandle.USER_SYSTEM) {
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ // Original value.
+ final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
+
+ // Save the restriction to ActiveAdmin.
+ // TODO When DO sets a restriction, it'll always be treated as device-wide.
+ // If there'll be a policy that can be set by both, we'll need scoping support,
+ // and need to have another Bundle in DO active admin to hold restrictions as
+ // PO.
+ activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+ saveSettingsLocked(userHandle);
+
+ // Tell UserManager the new value. Note this needs to be done before calling
+ // into AudioService, because AS will check AppOps that'll be updated by UM.
+ if (isDeviceOwner) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+ } else {
+ mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+ }
+
+ // New value.
+ final boolean enabled = mUserManager.hasUserRestriction(key, user);
+
+ // TODO The rest of the code should move to UserManagerService.
+
+ if (enabled && !alreadyRestricted) {
+ if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+ mInjector.getIAudioService()
+ .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
+ } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+ mInjector.getIAudioService()
+ .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
+ } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
+ mInjector.settingsSecurePutIntForUser(
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
+ userHandle);
+ } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+ mInjector.settingsSecurePutIntForUser(
+ Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF,
+ userHandle);
+ mInjector.settingsSecurePutStringForUser(
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+ userHandle);
+ } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
+ // Only disable adb if changing for system user, since it is global
+ // TODO: should this be admin user?
+ if (userHandle == UserHandle.USER_SYSTEM) {
+ mInjector.settingsGlobalPutStringForUser(
+ Settings.Global.ADB_ENABLED, "0", userHandle);
+ }
+ } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
mInjector.settingsGlobalPutStringForUser(
- Settings.Global.ADB_ENABLED, "0", userHandle);
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+ userHandle);
+ mInjector.settingsGlobalPutStringForUser(
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+ userHandle);
+ } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
+ mInjector.settingsSecurePutIntForUser(
+ Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+ userHandle);
}
- } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
- userHandle);
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
- userHandle);
- } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
- userHandle);
}
- }
- mUserManager.setUserRestriction(key, enabled, user);
- activeAdmin.ensureUserRestrictions().putBoolean(key, enabled);
- saveSettingsLocked(userHandle);
- if (enabled != alreadyRestricted) {
- if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- // Send out notifications however as some clients may want to reread the
- // value which actually changed due to a restriction having been applied.
- final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
- long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
- mInjector.systemPropertiesSet(property, Long.toString(version));
+ if (enabled != alreadyRestricted) {
+ if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+ // Send out notifications however as some clients may want to reread the
+ // value which actually changed due to a restriction having been
+ // applied.
+ final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+ long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
+ mInjector.systemPropertiesSet(property, Long.toString(version));
- final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
- Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
- mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+ final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+ Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+ mContext.getContentResolver().notifyChange(url, null, true, userHandle);
+ }
}
- }
- if (!enabled && alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(false, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+ if (!enabled && alreadyRestricted) {
+ if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+ mInjector.getIAudioService()
+ .setMicrophoneMute(false, mContext.getPackageName(),
+ userHandle);
+ } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+ mInjector.getIAudioService()
+ .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
+ }
}
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
}
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
+
+ sendChangedNotification(userHandle);
}
- sendChangedNotification(userHandle);
}
}
@@ -5650,7 +5778,7 @@
long id = mInjector.binderClearCallingIdentity();
try {
- if (DBG) {
+ if (VERBOSE_LOG) {
Slog.v(LOG_TAG, "installing " + packageName + " for "
+ userId);
}
@@ -5705,7 +5833,9 @@
0, // no flags
primaryUser.id);
- if (DBG) Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+ if (VERBOSE_LOG) {
+ Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
+ }
int numberOfAppsInstalled = 0;
if (activitiesToEnable != null) {
for (ResolveInfo info : activitiesToEnable) {
@@ -6275,7 +6405,8 @@
}
}
- private final class LocalService extends DevicePolicyManagerInternal {
+ @VisibleForTesting
+ final class LocalService extends DevicePolicyManagerInternal {
private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners;
@Override
@@ -6322,6 +6453,30 @@
}
}
+ @Override
+ public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
+ synchronized (DevicePolicyManagerService.this) {
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+
+ final Bundle deviceOwnerRestrictions =
+ deviceOwner == null ? null : deviceOwner.userRestrictions;
+ final Bundle profileOwnerRestrictions =
+ profileOwner == null ? null : profileOwner.userRestrictions;
+
+ if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+ // No restrictions to merge.
+ return inBundle;
+ }
+
+ final Bundle composed = new Bundle(inBundle);
+ UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
+ UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
+
+ return composed;
+ }
+ }
+
private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 799267d..12b3775 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -30,7 +30,6 @@
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -75,6 +74,7 @@
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_COMPONENT_NAME = "component";
private static final String ATTR_USERID = "userId";
+ private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated";
private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
@@ -155,7 +155,16 @@
Slog.e(TAG, "Invalid user id for device owner user: " + userId);
return;
}
- mDeviceOwner = new OwnerInfo(ownerName, admin);
+ // For a newly set DO, there's no need for migration.
+ setDeviceOwnerWithRestrictionsMigrated(admin, ownerName, userId,
+ /* userRestrictionsMigrated =*/ true);
+ }
+
+ // Note this should be only called during migration. Normally when DO is set,
+ // userRestrictionsMigrated should always be true.
+ void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId,
+ boolean userRestrictionsMigrated) {
+ mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
mDeviceOwnerUserId = userId;
}
@@ -165,7 +174,9 @@
}
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
- mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
+ // For a newly set PO, there's no need for migration.
+ mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
+ /* userRestrictionsMigrated =*/ true));
}
void removeProfileOwner(int userId) {
@@ -207,6 +218,38 @@
return mDeviceOwner != null;
}
+ /**
+ * @return true if user restrictions need to be migrated for DO.
+ */
+ boolean getDeviceOwnerUserRestrictionsNeedsMigration() {
+ return mDeviceOwner != null && !mDeviceOwner.userRestrictionsMigrated;
+ }
+
+ /**
+ * @return true if user restrictions need to be migrated for PO.
+ */
+ boolean getProfileOwnerUserRestrictionsNeedsMigration(int userId) {
+ OwnerInfo profileOwner = mProfileOwners.get(userId);
+ return profileOwner != null && !profileOwner.userRestrictionsMigrated;
+ }
+
+ /** Sets the user restrictions migrated flag, and also writes to the file. */
+ void setDeviceOwnerUserRestrictionsMigrated() {
+ if (mDeviceOwner != null) {
+ mDeviceOwner.userRestrictionsMigrated = true;
+ }
+ writeDeviceOwner();
+ }
+
+ /** Sets the user restrictions migrated flag, and also writes to the file. */
+ void setProfileOwnerUserRestrictionsMigrated(int userId) {
+ OwnerInfo profileOwner = mProfileOwners.get(userId);
+ if (profileOwner != null) {
+ profileOwner.userRestrictionsMigrated = true;
+ }
+ writeProfileOwner(userId);
+ }
+
private boolean readLegacyOwnerFile(File file) {
if (!file.exists()) {
// Already migrated or the device has no owners.
@@ -226,7 +269,8 @@
if (tag.equals(TAG_DEVICE_OWNER)) {
String name = parser.getAttributeValue(null, ATTR_NAME);
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
- mDeviceOwner = new OwnerInfo(name, packageName);
+ mDeviceOwner = new OwnerInfo(name, packageName,
+ /* userRestrictionsMigrated =*/ false);
mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
} else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
// Deprecated tag
@@ -241,7 +285,8 @@
ComponentName admin = ComponentName.unflattenFromString(
profileOwnerComponentStr);
if (admin != null) {
- profileOwnerInfo = new OwnerInfo(profileOwnerName, admin);
+ profileOwnerInfo = new OwnerInfo(profileOwnerName, admin,
+ /* userRestrictionsMigrated =*/ false);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -250,7 +295,8 @@
}
}
if (profileOwnerInfo == null) {
- profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName);
+ profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName,
+ /* userRestrictionsMigrated =*/ false);
}
mProfileOwners.put(userId, profileOwnerInfo);
} else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
@@ -503,21 +549,24 @@
}
}
- private static class OwnerInfo {
+ static class OwnerInfo {
public final String name;
public final String packageName;
public final ComponentName admin;
+ public boolean userRestrictionsMigrated;
- public OwnerInfo(String name, String packageName) {
+ public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated) {
this.name = name;
this.packageName = packageName;
this.admin = new ComponentName(packageName, "");
+ this.userRestrictionsMigrated = userRestrictionsMigrated;
}
- public OwnerInfo(String name, ComponentName admin) {
+ public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated) {
this.name = name;
this.admin = admin;
this.packageName = admin.getPackageName();
+ this.userRestrictionsMigrated = userRestrictionsMigrated;
}
public void writeToXml(XmlSerializer out, String tag) throws IOException {
@@ -529,6 +578,8 @@
if (admin != null) {
out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
}
+ out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED,
+ String.valueOf(userRestrictionsMigrated));
out.endTag(null, tag);
}
@@ -537,12 +588,16 @@
final String name = parser.getAttributeValue(null, ATTR_NAME);
final String componentName =
parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
+ final String userRestrictionsMigratedStr =
+ parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED);
+ final boolean userRestrictionsMigrated =
+ ("true".equals(userRestrictionsMigratedStr));
// Has component name? If so, return [name, component]
if (componentName != null) {
final ComponentName admin = ComponentName.unflattenFromString(componentName);
if (admin != null) {
- return new OwnerInfo(name, admin);
+ return new OwnerInfo(name, admin, userRestrictionsMigrated);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -552,7 +607,7 @@
}
// Else, build with [name, package]
- return new OwnerInfo(name, packageName);
+ return new OwnerInfo(name, packageName, userRestrictionsMigrated);
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1ec1a46..e32af5c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -438,7 +438,6 @@
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
ConsumerIrService consumerIr = null;
- AudioService audioService = null;
MmsServiceBroker mmsService = null;
EntropyMixer entropyMixer = null;
@@ -857,12 +856,7 @@
}
traceBeginAndSlog("StartAudioService");
- try {
- audioService = new AudioService(context);
- ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
- }
+ mSystemServiceManager.startService(AudioService.Lifecycle.class);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
if (!disableNonCoreServices) {
@@ -1163,7 +1157,6 @@
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
- final AudioService audioServiceF = audioService;
final MmsServiceBroker mmsServiceF = mmsService;
// We now tell the activity manager it is okay to run third party
@@ -1234,13 +1227,7 @@
reportWtf("making Connectivity Service ready", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeAudioServiceReady");
- try {
- if (audioServiceF != null) audioServiceF.systemReady();
- } catch (Throwable e) {
- reportWtf("Notifying AudioService running", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
Watchdog.getInstance().start();
// It is now okay to let the various system services start their
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 28cb114..c9efc69 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -417,9 +417,10 @@
encap, mTransactionId, getSecs(), clientAddress,
DO_UNICAST, mHwAddr, requestedAddress,
serverAddress, REQUESTED_PARAMS, null);
+ String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
" request=" + requestedAddress.getHostAddress() +
- " to=" + serverAddress.getHostAddress();
+ " serverid=" + serverStr;
return transmitPacket(packet, description, to);
}
@@ -822,7 +823,8 @@
public void enter() {
super.enter();
if (!setIpAddress(mDhcpLease.ipAddress) ||
- !connectUdpSock((mDhcpLease.serverAddress))) {
+ (mDhcpLease.serverAddress != null &&
+ !connectUdpSock((mDhcpLease.serverAddress)))) {
notifyFailure();
// There's likely no point in going into DhcpInitState here, we'll probably just
// repeat the transaction, get the same IP address as before, and fail.
@@ -878,11 +880,15 @@
}
protected boolean sendPacket() {
+ // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
+ // http://b/25343517 . Try to make things work anyway by using broadcast renews.
+ Inet4Address to = (mDhcpLease.serverAddress != null) ?
+ mDhcpLease.serverAddress : INADDR_BROADCAST;
return sendRequestPacket(
(Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
INADDR_ANY, // DHCP_REQUESTED_IP
- INADDR_ANY, // DHCP_SERVER_IDENTIFIER
- (Inet4Address) mDhcpLease.serverAddress); // packet destination address
+ null, // DHCP_SERVER_IDENTIFIER
+ to); // packet destination address
}
protected void receivePacket(DhcpPacket packet) {
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 53f55cd..88155f7 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -403,7 +403,6 @@
// TODO: simply the number of objects by making this extend Thread.
private final class NetlinkSocketObserver implements Runnable {
- private static final String TAG = "NetlinkSocketObserver";
private NetlinkSocket mSocket;
@Override
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
new file mode 100644
index 0000000..9564969
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<device-owner package="com.android.frameworks.servicestests" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="10" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2" />
+<profile-owner package="com.android.frameworks.servicestests" name="0" userId="11" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3" />
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
new file mode 100644
index 0000000..48cb814
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+ <admin name="com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver">
+ </admin>
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
new file mode 100644
index 0000000..6b53840
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2">
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
new file mode 100644
index 0000000..2bcc5d4
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3">
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b4f43..c12f978 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -876,11 +876,12 @@
}
private void expectSystemReady() throws Exception {
- mAlarmManager.remove(isA(PendingIntent.class));
+ mAlarmManager.remove(isA(PendingIntent.class), null);
expectLastCall().anyTimes();
- mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
+ mAlarmManager.set(getContext().getPackageName(),
+ eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
+ anyInt(), isA(PendingIntent.class), null, null, isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
new file mode 100644
index 0000000..dfa9f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Pair;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
+ private DpmMockContext mContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mContext = getContext();
+
+ when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+ .thenReturn(true);
+ }
+
+ public void testMigration() throws Exception {
+ final File user10dir = mMockContext.addUser(10, 0);
+ final File user11dir = mMockContext.addUser(11, UserInfo.FLAG_MANAGED_PROFILE);
+ final File user12dir = mMockContext.addUser(12, 0);
+
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ setUpPackageManagerForAdmin(admin2, UserHandle.getUid(10, 123));
+ setUpPackageManagerForAdmin(admin3, UserHandle.getUid(11, 456));
+
+ // Create the legacy owners & policies file.
+ DpmTestUtils.writeToFile(
+ (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
+ DpmTestUtils.readAsset(mRealTestContext,
+ "DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml"));
+
+ DpmTestUtils.writeToFile(
+ (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+ DpmTestUtils.readAsset(mRealTestContext,
+ "DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml"));
+
+ DpmTestUtils.writeToFile(
+ (new File(user10dir, "device_policies.xml")).getAbsoluteFile(),
+ DpmTestUtils.readAsset(mRealTestContext,
+ "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml"));
+ DpmTestUtils.writeToFile(
+ (new File(user11dir, "device_policies.xml")).getAbsoluteFile(),
+ DpmTestUtils.readAsset(mRealTestContext,
+ "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml"));
+
+ // Set up UserManager
+ when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_RECORD_AUDIO));
+
+ when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ eq(10))).thenReturn(DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_REMOVE_USER,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_WALLPAPER,
+ UserManager.DISALLOW_RECORD_AUDIO));
+
+ when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ eq(11))).thenReturn(DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_REMOVE_USER,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_WALLPAPER,
+ UserManager.DISALLOW_RECORD_AUDIO));
+
+ final Map<Integer, Bundle> newBaseRestrictions = new HashMap<>();
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Integer userId = (Integer) invocation.getArguments()[0];
+ Bundle bundle = (Bundle) invocation.getArguments()[1];
+
+ newBaseRestrictions.put(userId, bundle);
+
+ return null;
+ }
+ }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
+ anyInt(), any(Bundle.class));
+
+ // Initialize DPM/DPMS and let it migrate the persisted information.
+ // (Need clearCallingIdentity() to pass permission checks.)
+
+ final DevicePolicyManagerServiceTestable dpms;
+
+ final long ident = mContext.binder.clearCallingIdentity();
+ try {
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+
+ dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+ dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+ } finally {
+ mContext.binder.restoreCallingIdentity(ident);
+ }
+
+ // Now all information should be migrated.
+ assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
+
+ // Check the new base restrictions.
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_RECORD_AUDIO
+ ),
+ newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_RECORD_AUDIO
+ ),
+ newBaseRestrictions.get(10));
+
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_WALLPAPER,
+ UserManager.DISALLOW_RECORD_AUDIO
+ ),
+ newBaseRestrictions.get(11));
+
+ // Check the new owner restrictions.
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_ADD_USER
+ ),
+ dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions());
+
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_REMOVE_USER,
+ UserManager.DISALLOW_WALLPAPER
+ ),
+ dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions());
+
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_REMOVE_USER
+ ),
+ dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b109e7b..2c01b8a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -27,6 +27,7 @@
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.view.IWindowManager;
import java.io.File;
@@ -107,6 +108,11 @@
}
@Override
+ UserManagerInternal getUserManagerInternal() {
+ return context.userManagerInternal;
+ }
+
+ @Override
PowerManagerInternal getPowerManagerInternal() {
return context.powerManagerInternal;
}
@@ -153,7 +159,7 @@
@Override
String getDevicePolicyFilePathForSystemUser() {
- return context.systemUserDataDir.getAbsolutePath();
+ return context.systemUserDataDir.getAbsolutePath() + "/";
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d6a60c7..727858b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -27,7 +27,6 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -70,9 +69,6 @@
private DpmMockContext mContext;
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
- public ComponentName admin1;
- public ComponentName admin2;
- public ComponentName admin3;
@Override
protected void setUp() throws Exception {
@@ -85,10 +81,6 @@
initializeDpms();
- admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
- admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
- admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
-
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -113,67 +105,6 @@
}
}
- private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception {
- setUpPackageManagerForAdmin(admin, packageUid,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
- }
-
- private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
- int enabledSetting) throws Exception {
-
- // Set up queryBroadcastReceivers().
-
- final Intent resolveIntent = new Intent();
- resolveIntent.setComponent(admin);
- final List<ResolveInfo> realResolveInfo =
- mRealTestContext.getPackageManager().queryBroadcastReceivers(
- resolveIntent,
- PackageManager.GET_META_DATA);
- assertNotNull(realResolveInfo);
- assertEquals(1, realResolveInfo.size());
-
- // We need to change AI, so set a clone.
- realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
-
- // We need to rewrite the UID in the activity info.
- realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
-
- doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers(
- MockUtils.checkIntentComponent(admin),
- eq(PackageManager.GET_META_DATA
- | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
- eq(UserHandle.getUserId(packageUid)));
-
- // Set up getApplicationInfo().
-
- final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
- mRealTestContext.getPackageManager().getApplicationInfo(
- admin1.getPackageName(),
- PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
-
- ai.enabledSetting = enabledSetting;
- ai.uid = packageUid;
-
- doReturn(ai).when(mContext.ipackageManager).getApplicationInfo(
- eq(admin1.getPackageName()),
- eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
- eq(UserHandle.getUserId(packageUid)));
-
- // Set up getPackageInfo().
-
- final PackageInfo pi = DpmTestUtils.cloneParcelable(
- mRealTestContext.getPackageManager().getPackageInfo(
- admin1.getPackageName(), 0));
- assertTrue(pi.applicationInfo.flags != 0);
-
- pi.applicationInfo.uid = packageUid;
-
- doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
- eq(admin1.getPackageName()),
- eq(0),
- eq(UserHandle.getUserId(packageUid)));
- }
-
private void setUpUserManager() {
// Emulate UserManager.set/getApplicationRestriction().
final Map<Pair<String, UserHandle>, Bundle> appRestrictions = new HashMap<>();
@@ -220,7 +151,7 @@
assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
// Check
- assertEquals(admin1, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
+ assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
}
public void testHasNoFeature() throws Exception {
@@ -743,6 +674,8 @@
assertEquals("", dpms.getDeviceOwner().getClassName());
// Then create a new DPMS to have it load the settings from files.
+ when(mContext.userManager.getUserRestrictions(any(UserHandle.class)))
+ .thenReturn(new Bundle());
initializeDpms();
// Now the DO component name is a full name.
@@ -802,32 +735,33 @@
assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
UserHandle.USER_SYSTEM));
- assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_SMS));
- assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+ );
dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_SMS));
- assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+ dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+ );
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
- assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_SMS));
- assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+ dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+ );
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_SMS));
- assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
+ );
// TODO Check inner calls.
// TODO Make sure restrictions are written to the file.
@@ -836,42 +770,106 @@
public void testSetUserRestriction_asPo() {
setAsProfileOwner(admin1);
- assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
- assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .ensureUserRestrictions()
+ );
dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
- assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_OUTGOING_CALLS
+ ),
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .ensureUserRestrictions()
+ );
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
- assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
- assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_OUTGOING_CALLS
+ ),
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .ensureUserRestrictions()
+ );
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES));
- assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
- .ensureUserRestrictions()
- .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+ .ensureUserRestrictions()
+ );
// TODO Check inner calls.
// TODO Make sure restrictions are written to the file.
}
+
+ public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
+ final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
+
+ Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+ UserHandle.USER_SYSTEM, in);
+ assertTrue(in == actual);
+
+ actual = dpms.mLocalService.getComposedUserRestrictions(
+ DpmMockContext.CALLER_USER_HANDLE, in);
+ assertTrue(in == actual);
+ }
+
+ public void testGetComposedUserRestrictions() throws Exception {
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+ // First, set DO.
+
+ // Call from a process on the system user.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+ // Make sure admin1 is installed on system user.
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+ // Call.
+ dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
+ assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
+ UserHandle.USER_SYSTEM));
+
+ dpm.addUserRestriction(admin1, "rest1");
+ dpm.addUserRestriction(admin1, "rest2");
+
+ // Set PO on CALLER_USER_HANDLE.
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ setAsProfileOwner(admin2);
+
+ dpm.addUserRestriction(admin2, "restA");
+ dpm.addUserRestriction(admin2, "restB");
+
+ final Bundle in = DpmTestUtils.newRestrictions("abc");
+
+ Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
+ UserHandle.USER_SYSTEM, in);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+ actual);
+
+ actual = dpms.mLocalService.getComposedUserRestrictions(
+ DpmMockContext.CALLER_USER_HANDLE, in);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
+ actual);
+
+ actual = dpms.mLocalService.getComposedUserRestrictions(
+ DpmMockContext.CALLER_USER_HANDLE + 1, in);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
+ actual);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index d1b4835..cc337b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -36,6 +36,7 @@
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
import android.view.IWindowManager;
@@ -203,6 +204,7 @@
public final EnvironmentForMock environment;
public final SystemPropertiesForMock systemProperties;
public final UserManager userManager;
+ public final UserManagerInternal userManagerInternal;
public final UserManagerForMock userManagerForMock;
public final PowerManagerForMock powerManager;
public final PowerManagerInternal powerManagerInternal;
@@ -233,6 +235,7 @@
environment = mock(EnvironmentForMock.class);
systemProperties= mock(SystemPropertiesForMock.class);
userManager = mock(UserManager.class);
+ userManagerInternal = mock(UserManagerInternal.class);
userManagerForMock = mock(UserManagerForMock.class);
powerManager = mock(PowerManagerForMock.class);
powerManagerInternal = mock(PowerManagerInternal.class);
@@ -257,6 +260,9 @@
// System user is always running.
setUserRunning(UserHandle.USER_SYSTEM, true);
+
+ // This method must return an object.
+ when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
}
public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 63bf125..e11f3fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -16,10 +16,21 @@
package com.android.server.devicepolicy;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
import android.test.AndroidTestCase;
import java.io.File;
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
public abstract class DpmTestBase extends AndroidTestCase {
public static final String TAG = "DpmTest";
@@ -29,6 +40,10 @@
public File dataDir;
+ public ComponentName admin1;
+ public ComponentName admin2;
+ public ComponentName admin3;
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -37,10 +52,77 @@
mMockContext = new DpmMockContext(
mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
+
+ admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
+ admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
+ admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
}
@Override
public DpmMockContext getContext() {
return mMockContext;
}
+
+
+ protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
+ throws Exception {
+ setUpPackageManagerForAdmin(admin, packageUid,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+ }
+
+ protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
+ int enabledSetting) throws Exception {
+
+ // Set up queryBroadcastReceivers().
+
+ final Intent resolveIntent = new Intent();
+ resolveIntent.setComponent(admin);
+ final List<ResolveInfo> realResolveInfo =
+ mRealTestContext.getPackageManager().queryBroadcastReceivers(
+ resolveIntent,
+ PackageManager.GET_META_DATA);
+ assertNotNull(realResolveInfo);
+ assertEquals(1, realResolveInfo.size());
+
+ // We need to change AI, so set a clone.
+ realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
+
+ // We need to rewrite the UID in the activity info.
+ realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
+
+ doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceivers(
+ MockUtils.checkIntentComponent(admin),
+ eq(PackageManager.GET_META_DATA
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+ eq(UserHandle.getUserId(packageUid)));
+
+ // Set up getApplicationInfo().
+
+ final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+ mRealTestContext.getPackageManager().getApplicationInfo(
+ admin.getPackageName(),
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
+
+ ai.enabledSetting = enabledSetting;
+ ai.uid = packageUid;
+
+ doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
+ eq(admin.getPackageName()),
+ eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+ eq(UserHandle.getUserId(packageUid)));
+
+ // Set up getPackageInfo().
+
+ final PackageInfo pi = DpmTestUtils.cloneParcelable(
+ mRealTestContext.getPackageManager().getPackageInfo(
+ admin.getPackageName(), 0));
+ assertTrue(pi.applicationInfo.flags != 0);
+
+ pi.applicationInfo.uid = packageUid;
+
+ doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
+ eq(admin.getPackageName()),
+ eq(0),
+ eq(UserHandle.getUserId(packageUid)));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index 7506273..cceb2d2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -16,21 +16,35 @@
package com.android.server.devicepolicy;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import android.content.Context;
+import android.os.Bundle;
import android.os.FileUtils;
import android.os.Parcel;
import android.os.Parcelable;
+import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Printer;
import org.junit.Assert;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
-public class DpmTestUtils {
- private DpmTestUtils() {
- }
+import junit.framework.AssertionFailedError;
+public class DpmTestUtils extends AndroidTestCase {
public static void clearDir(File dir) {
if (dir.exists()) {
Assert.assertTrue("failed to delete dir", FileUtils.deleteContents(dir));
@@ -43,6 +57,44 @@
return list == null ? 0 : list.size();
}
+ public static Bundle newRestrictions(String... restrictions) {
+ final Bundle ret = new Bundle();
+ for (String restriction : restrictions) {
+ ret.putBoolean(restriction, true);
+ }
+ return ret;
+ }
+
+ public static void assertRestrictions(Bundle expected, Bundle actual) {
+ final ArrayList<String> elist;
+ if (expected == null) {
+ elist = null;
+ } else {
+ elist = Lists.newArrayList();
+ for (String key : expected.keySet()) {
+ if (expected.getBoolean(key)) {
+ elist.add(key);
+ }
+ }
+ Collections.sort(elist);
+ }
+
+ final ArrayList<String> alist;
+ if (actual == null) {
+ alist = null;
+ } else {
+ alist = Lists.newArrayList();
+ for (String key : actual.keySet()) {
+ if (actual.getBoolean(key)) {
+ alist.add(key);
+ }
+ }
+ Collections.sort(alist);
+ }
+
+ assertEquals(elist, alist);
+ }
+
public static <T extends Parcelable> T cloneParcelable(T source) {
Parcel p = Parcel.obtain();
p.writeParcelable(source, 0);
@@ -58,4 +110,57 @@
Log.i(DpmTestBase.TAG, x);
}
};
+
+ public static String readAsset(Context context, String assetPath) throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(
+ context.getResources().getAssets().open(assetPath)))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append(System.lineSeparator());
+ }
+ }
+ return sb.toString();
+ }
+
+ public static void writeToFile(File path, String content)
+ throws IOException {
+ path.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(path)) {
+ Log.i(DpmTestBase.TAG, "Writing to " + path);
+ Log.i(DpmTestBase.TAG, content);
+ writer.write(content);
+ }
+ }
+
+ private static boolean checkAssertRestrictions(Bundle a, Bundle b) {
+ try {
+ assertRestrictions(a, b);
+ return true;
+ } catch (AssertionFailedError e) {
+ return false;
+ }
+ }
+
+ public void testAssertRestrictions() {
+ final Bundle a = newRestrictions();
+ final Bundle b = newRestrictions("a");
+ final Bundle c = newRestrictions("a");
+ final Bundle d = newRestrictions("b", "c");
+ final Bundle e = newRestrictions("b", "c");
+
+ assertTrue(checkAssertRestrictions(null, null));
+ assertFalse(checkAssertRestrictions(null, a));
+ assertFalse(checkAssertRestrictions(a, null));
+ assertTrue(checkAssertRestrictions(a, a));
+
+ assertFalse(checkAssertRestrictions(a, b));
+ assertTrue(checkAssertRestrictions(b, c));
+
+ assertFalse(checkAssertRestrictions(c, d));
+ assertTrue(checkAssertRestrictions(d, e));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 79845d2..4e11762 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -47,31 +47,6 @@
(mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
*/
public class OwnersTest extends DpmTestBase {
- private String readAsset(String assetPath) throws IOException {
- final StringBuilder sb = new StringBuilder();
- try (BufferedReader br = new BufferedReader(
- new InputStreamReader(
- mRealTestContext.getResources().getAssets().open(assetPath)))) {
- String line;
- while ((line = br.readLine()) != null) {
- sb.append(line);
- sb.append(System.lineSeparator());
- }
- }
- return sb.toString();
- }
-
- private void createLegacyFile(File path, String content)
- throws IOException {
- path.getParentFile().mkdirs();
-
- try (FileWriter writer = new FileWriter(path)) {
- Log.i(TAG, "Writing to " + path);
- Log.i(TAG, content);
- writer.write(content);
- }
- }
-
public void testUpgrade01() throws Exception {
getContext().addUsers(10, 11, 20, 21);
@@ -79,8 +54,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test01/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
owners.load();
@@ -99,6 +74,12 @@
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -110,6 +91,12 @@
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
}
@@ -120,8 +107,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test02/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
owners.load();
@@ -142,6 +129,12 @@
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -156,6 +149,12 @@
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
}
@@ -166,8 +165,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test03/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
owners.load();
@@ -196,6 +195,12 @@
owners.getProfileOwnerComponent(11));
assertEquals("1", owners.getProfileOwnerName(11));
assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -218,9 +223,19 @@
owners.getProfileOwnerComponent(11));
assertEquals("1", owners.getProfileOwnerName(11));
assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
}
+ /**
+ * Note this also tests {@link Owners#setDeviceOwnerUserRestrictionsMigrated()}
+ * and {@link Owners#setProfileOwnerUserRestrictionsMigrated(int)}.
+ */
public void testUpgrade04() throws Exception {
getContext().addUsers(10, 11, 20, 21);
@@ -228,8 +243,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test04/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
owners.load();
@@ -262,6 +277,12 @@
owners.getProfileOwnerComponent(11));
assertEquals("1", owners.getProfileOwnerName(11));
assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+ assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -288,6 +309,40 @@
owners.getProfileOwnerComponent(11));
assertEquals("1", owners.getProfileOwnerName(11));
assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11));
+
+ assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+ owners.setDeviceOwnerUserRestrictionsMigrated();
+ }
+
+ {
+ final OwnersTestable owners = new OwnersTestable(getContext());
+ owners.load();
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+ owners.setProfileOwnerUserRestrictionsMigrated(11);
+ }
+
+ {
+ final OwnersTestable owners = new OwnersTestable(getContext());
+ owners.load();
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
+
+ owners.setProfileOwnerUserRestrictionsMigrated(11);
}
}
@@ -298,8 +353,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test05/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
owners.load();
@@ -319,6 +374,12 @@
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -332,6 +393,12 @@
assertNull(owners.getSystemUpdatePolicy());
assertEquals(0, owners.getProfileOwnerKeys().size());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
}
@@ -342,8 +409,8 @@
{
final OwnersTestable owners = new OwnersTestable(getContext());
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test06/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
owners.load();
@@ -362,6 +429,12 @@
assertNotNull(owners.getSystemUpdatePolicy());
assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
// Then re-read and check.
@@ -375,6 +448,12 @@
assertNotNull(owners.getSystemUpdatePolicy());
assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType());
+
+ assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20));
+ assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21));
}
}
@@ -384,8 +463,8 @@
final OwnersTestable owners = new OwnersTestable(getContext());
// First, migrate to create new-style config files.
- createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
- readAsset("OwnersTest/test04/input.xml"));
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
owners.load();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 86f5ed7..66c7dbb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -211,11 +211,14 @@
public void testRestrictions() {
UserInfo testUser = createUser("User 1", 0);
- Bundle restrictions = new Bundle();
- restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
- restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false);
- mUserManager.setUserRestrictions(restrictions, new UserHandle(testUser.id));
+
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_INSTALL_APPS, true, new UserHandle(testUser.id));
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_CONFIG_WIFI, false, new UserHandle(testUser.id));
+
Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id));
+ // Note this will fail if DO already sets those restrictions.
assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 31763e7..129e537 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -212,6 +212,10 @@
}
private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
+ if (DEBUG) {
+ Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")");
+ }
+
AlsaDevice testDevice = new AlsaDevice(type, card, device);
// This value was empirically determined.
@@ -292,7 +296,8 @@
*/
/* package */ UsbAudioDevice selectAudioCard(int card) {
if (DEBUG) {
- Slog.d(TAG, "selectAudioCard() card:" + card);
+ Slog.d(TAG, "selectAudioCard() card:" + card
+ + " isCardUsb(): " + mCardsParser.isCardUsb(card));
}
if (!mCardsParser.isCardUsb(card)) {
// Don't. AudioPolicyManager has logic for falling back to internal devices.
@@ -304,6 +309,10 @@
boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
+ if (DEBUG) {
+ Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
+ }
+
int deviceClass =
(mCardsParser.isCardUsb(card)
? UsbAudioDevice.kAudioDeviceClass_External
@@ -320,10 +329,6 @@
return null;
}
- if (DEBUG) {
- Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
- }
-
UsbAudioDevice audioDevice =
new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
@@ -339,14 +344,13 @@
if (DEBUG) {
Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
}
- mCardsParser.scan();
return selectAudioCard(mCardsParser.getDefaultCard());
}
/* package */ void usbDeviceAdded(UsbDevice usbDevice) {
if (DEBUG) {
Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
- "nm:" + usbDevice.getProductName());
+ " nm:" + usbDevice.getProductName());
}
// Is there an audio interface in there?
@@ -361,27 +365,22 @@
isAudioDevice = true;
}
}
+
+ if (DEBUG) {
+ Slog.d(TAG, " isAudioDevice: " + isAudioDevice);
+ }
if (!isAudioDevice) {
return;
}
- ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords();
- mCardsParser.scan();
-
- int addedCard = -1;
- ArrayList<AlsaCardsParser.AlsaCardRecord>
- newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs);
- if (newScanRecs.size() > 0) {
- // This is where we select the just connected device
- // NOTE - to switch to prefering the first-connected device, just always
- // take the else clause below.
- addedCard = newScanRecs.get(0).mCardNum;
- } else {
- addedCard = mCardsParser.getDefaultUsbCard();
- }
+ int addedCard = mCardsParser.getDefaultUsbCard();
// If the default isn't a USB device, let the existing "select internal mechanism"
// handle the selection.
+ if (DEBUG) {
+ Slog.d(TAG, " mCardsParser.isCardUsb(" + addedCard + ") = "
+ + mCardsParser.isCardUsb(addedCard));
+ }
if (mCardsParser.isCardUsb(addedCard)) {
UsbAudioDevice audioDevice = selectAudioCard(addedCard);
if (audioDevice != null) {
@@ -429,6 +428,10 @@
}
}
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "deviceAdded() - done");
+ }
}
/* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
@@ -439,7 +442,7 @@
UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
if (audioDevice != null) {
- if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
+ if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
notifyDeviceState(audioDevice, false);
// if there any external devices left, select one of them
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 3160e39..e0f95cf 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -756,6 +756,8 @@
if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) {
Slog.v(TAG, "Current user switched to " + mCurrentUser
+ "; resetting USB host stack for MTP or PTP");
+ // avoid leaking sensitive data from previous user
+ mUsbDataUnlocked = false;
setEnabledFunctions(mCurrentFunctions, true);
}
mCurrentUser = msg.arg1;
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index f263b4d..861a379 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -178,6 +178,7 @@
* Codec: Codec info.
* DisplayText: Display text for the call.
* AdditionalCallInfo: Additional call info.
+ * CallRadioTech: The radio tech on which the call is placed.
*/
public static final String EXTRA_OI = "oi";
public static final String EXTRA_CNA = "cna";
@@ -187,6 +188,7 @@
public static final String EXTRA_CODEC = "Codec";
public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+ public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
public int mServiceType;
public int mCallType;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index becf99b..cc5e981 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -105,11 +105,9 @@
.addSimple(u"@android:id/one", ResourceId(0x01020000))
.addSimple(u"@android:id/two", ResourceId(0x01020001))
.addSimple(u"@android:id/three", ResourceId(0x01020002))
+ .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+ .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
.build();
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
- SymbolState::kPublic, &diag));
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
- SymbolState::kPrivate, &diag));
JavaClassGeneratorOptions options;
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index bfef9d0..44710eb 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -186,6 +186,7 @@
Source source;
ResourceId id;
SymbolState symbolState = SymbolState::kUndefined;
+ std::u16string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> childResources;
};
@@ -194,7 +195,11 @@
static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
IDiagnostics* diag, ParsedResource* res) {
if (res->symbolState != SymbolState::kUndefined) {
- if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+ Symbol symbol;
+ symbol.state = res->symbolState;
+ symbol.source = res->source;
+ symbol.comment = res->comment;
+ if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
return false;
}
}
@@ -203,7 +208,11 @@
return true;
}
- if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
+ // Attach the comment, source and config to the value.
+ res->value->setComment(std::move(res->comment));
+ res->value->setSource(std::move(res->source));
+
+ if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
return false;
}
@@ -275,6 +284,7 @@
ParsedResource parsedResource;
parsedResource.name.entry = maybeName.value().toString();
parsedResource.source = mSource.withLine(parser->getLineNumber());
+ parsedResource.comment = std::move(comment);
bool result = true;
if (elementName == u"id") {
@@ -368,8 +378,8 @@
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
- bool allowRawValue) {
+std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
std::u16string rawValue;
@@ -386,8 +396,9 @@
auto onCreateReference = [&](const ResourceName& name) {
// name.package can be empty here, as it will assume the package name of the table.
- mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
- mDiag);
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(mSource.withLine(beginXmlLine));
+ mTable->addResource(name, {}, std::move(id), mDiag);
};
// Process the raw value.
@@ -411,11 +422,12 @@
mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
}
- // We can't parse this so return a RawString if we are allowed.
if (allowRawValue) {
+ // We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
+
return {};
}
@@ -683,8 +695,8 @@
}
return Attribute::Symbol{
- Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
- val.data };
+ Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())),
+ val.data };
}
static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 34c68d7..2d5a29d 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -65,12 +65,13 @@
StyleString* outStyleString);
/*
- * Parses the XML subtree and converts it to an Item. The type of Item that can be
- * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
- * can not be parsed as a regular Item, then a RawString is returned. Otherwise
- * this returns nullptr.
+ * Parses the XML subtree and returns an Item.
+ * The type of Item that can be parsed is denoted by the `typeMask`.
+ * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
+ * RawString is returned. Otherwise this returns false;
*/
- std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
+ std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue);
bool parseResources(XmlPullParser* parser);
bool parseString(XmlPullParser* parser, ParsedResource* outResource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a7e9d39..af6bf67 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -379,18 +379,39 @@
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
- std::string input = "<!-- This is a comment -->\n"
+ std::string input = "<!--This is a comment-->\n"
"<string name=\"foo\">Hi</string>";
ASSERT_TRUE(testParse(input));
- Maybe<ResourceTable::SearchResult> result = mTable.findResource(
- test::parseNameOrDie(u"@string/foo"));
- AAPT_ASSERT_TRUE(result);
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"This is a comment");
+}
- ResourceEntry* entry = result.value().entry;
- ASSERT_NE(entry, nullptr);
- ASSERT_FALSE(entry->values.empty());
- EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
+ std::string input = "<!--One-->\n"
+ "<!--Two-->\n"
+ "<string name=\"foo\">Hi</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"Two");
+}
+
+TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
+ std::string input = "<!--One-->\n"
+ "<string name=\"foo\">\n"
+ " Hi\n"
+ "<!--Two-->\n"
+ "</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"One");
}
/*
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 84674e8..fa4b109 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -19,6 +19,8 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+
+#include "util/Comparators.h"
#include "util/Util.h"
#include <algorithm>
@@ -29,10 +31,6 @@
namespace aapt {
-static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
- return lhs.config < rhs;
-}
-
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
return lhs->type < rhs;
}
@@ -191,52 +189,50 @@
static constexpr const char16_t* kValidNameMangledChars = u"._-$";
bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
- diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, IDiagnostics* diag) {
- return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source,
- util::make_unique<FileReference>(stringPool.makeRef(path)),
- kValidNameChars, diag);
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ stringPool.makeRef(path));
+ fileRef->setSource(source);
+ return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
- const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
- kValidNameMangledChars, diag);
+ return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
+ diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ResourceId id,
const ConfigDescription& config,
- const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, id, config, source, std::move(value),
- kValidNameMangledChars, diag);
+ return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
}
bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, const char16_t* validChars,
- IDiagnostics* diag) {
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ const char16_t* validChars, IDiagnostics* diag) {
+ assert(value && "value can't be nullptr");
+ assert(diag && "diagnostics can't be nullptr");
+
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -249,7 +245,7 @@
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -263,7 +259,7 @@
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -277,7 +273,7 @@
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -288,20 +284,20 @@
}
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
if (iter == endIter || iter->config != config) {
// This resource did not exist before, add it.
- entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
+ entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
} else {
int collisionResult = resolveValueCollision(iter->value.get(), value.get());
if (collisionResult > 0) {
// Take the incoming value.
- *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
+ iter->value = std::move(value);
} else if (collisionResult == 0) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "duplicate value for resource '" << name << "' "
- << "with config '" << iter->config << "'");
- diag->error(DiagMessage(iter->source)
+ << "with config '" << config << "'");
+ diag->error(DiagMessage(iter->value->getSource())
<< "resource previously defined here");
return false;
}
@@ -316,27 +312,29 @@
}
bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
}
-bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state,
- IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
}
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state,
- const char16_t* validChars, IDiagnostics* diag) {
- if (state == SymbolState::kUndefined) {
+ const Symbol& symbol, const char16_t* validChars,
+ IDiagnostics* diag) {
+ assert(diag && "diagnostics can't be nullptr");
+
+ if (symbol.state == SymbolState::kUndefined) {
// Nothing to do.
return true;
}
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -349,7 +347,7 @@
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -363,7 +361,7 @@
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -377,7 +375,7 @@
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -388,15 +386,14 @@
}
// Only mark the type state as public, it doesn't care about being private.
- if (state == SymbolState::kPublic) {
+ if (symbol.state == SymbolState::kPublic) {
type->symbolStatus.state = SymbolState::kPublic;
}
// Downgrading to a private symbol from a public one is not allowed.
if (entry->symbolStatus.state != SymbolState::kPublic) {
- if (entry->symbolStatus.state != state) {
- entry->symbolStatus.state = state;
- entry->symbolStatus.source = source;
+ if (entry->symbolStatus.state != symbol.state) {
+ entry->symbolStatus = std::move(symbol);
}
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index be90936..980504b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -47,12 +47,10 @@
};
/**
- * The resource value for a specific configuration.
+ * Represents a value defined for a given configuration.
*/
struct ResourceConfigValue {
ConfigDescription config;
- Source source;
- std::u16string comment;
std::unique_ptr<Value> value;
};
@@ -158,12 +156,11 @@
static int resolveValueCollision(Value* existing, Value* incoming);
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path, IDiagnostics* diag);
@@ -174,18 +171,18 @@
* names.
*/
bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
- const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
+ const ConfigDescription& config, std::unique_ptr<Value> value,
IDiagnostics* diag);
- bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
- SymbolState state, IDiagnostics* diag);
+ bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag);
+
bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, IDiagnostics* diag);
+ const Symbol& symbol, IDiagnostics* diag);
+
struct SearchResult {
ResourceTablePackage* package;
ResourceTableType* type;
@@ -224,13 +221,11 @@
private:
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
- bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, const char16_t* validChars,
- IDiagnostics* diag);
- bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, const char16_t* validChars,
- IDiagnostics* diag);
+ bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ const char16_t* validChars, IDiagnostics* diag);
+ bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
+ const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2055a80..42508fe 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -19,7 +19,7 @@
#include "ResourceValues.h"
#include "util/Util.h"
-#include "test/Common.h"
+#include "test/Builders.h"
#include <algorithm>
#include <gtest/gtest.h>
@@ -42,22 +42,26 @@
ResourceTable table;
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
- {}, Source{ "test.xml", 21 },
- util::make_unique<Id>(), &mDiagnostics));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ &mDiagnostics));
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
- {}, Source{ "test.xml", 21 },
- util::make_unique<Id>(), &mDiagnostics));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ &mDiagnostics));
}
TEST_F(ResourceTableTest, AddOneResource) {
ResourceTable table;
- EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
- Source{ "test/path/file.xml", 23 },
- util::make_unique<Id>(), &mDiagnostics));
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>()
+ .setSource("test/path/file.xml", 23u).build(),
+ &mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
}
@@ -71,23 +75,29 @@
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/layout_width"),
- config, Source{ "test/path/file.xml", 10 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/id"),
- config, Source{ "test/path/file.xml", 12 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
- config, Source{ "test/path/file.xml", 14 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
- languageConfig, Source{ "test/path/file.xml", 20 },
- util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
+ languageConfig,
+ test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .setSource("test/path/file.xml", 20u)
+ .build(),
+ &mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
@@ -99,14 +109,14 @@
TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
util::make_unique<Attribute>(true), &mDiagnostics));
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"), {}, {},
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
util::make_unique<Attribute>(false), &mDiagnostics));
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index ecc5cd2..f312d75 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,11 +15,12 @@
*/
#include "Resource.h"
-#include "flatten/ResourceTypeExtensions.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "ValueVisitor.h"
+#include "util/Util.h"
+#include "flatten/ResourceTypeExtensions.h"
+
#include <androidfw/ResourceTypes.h>
#include <limits>
@@ -35,18 +36,10 @@
visitor->visit(static_cast<Derived*>(this));
}
-bool Value::isItem() const {
- return false;
-}
-
bool Value::isWeak() const {
return false;
}
-bool Item::isItem() const {
- return true;
-}
-
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 0dae091..2629153 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -41,17 +41,42 @@
virtual ~Value() = default;
/**
- * Whether or not this is an Item.
- */
- virtual bool isItem() const;
-
- /**
* Whether this value is weak and can be overridden without
* warning or error. Default for base class is false.
*/
virtual bool isWeak() const;
/**
+ * Returns the source where this value was defined.
+ */
+ const Source& getSource() const {
+ return mSource;
+ }
+
+ void setSource(const Source& source) {
+ mSource = source;
+ }
+
+ void setSource(Source&& source) {
+ mSource = std::move(source);
+ }
+
+ /**
+ * Returns the comment that was associated with this resource.
+ */
+ StringPiece16 getComment() const {
+ return mComment;
+ }
+
+ void setComment(const StringPiece16& str) {
+ mComment = str.toString();
+ }
+
+ void setComment(std::u16string&& str) {
+ mComment = std::move(str);
+ }
+
+ /**
* Calls the appropriate overload of ValueVisitor.
*/
virtual void accept(RawValueVisitor* visitor) = 0;
@@ -65,6 +90,10 @@
* Human readable printout of this value.
*/
virtual void print(std::ostream* out) const = 0;
+
+private:
+ Source mSource;
+ std::u16string mComment;
};
/**
@@ -80,11 +109,6 @@
*/
struct Item : public Value {
/**
- * An Item is, of course, an Item.
- */
- virtual bool isItem() const override;
-
- /**
* Clone the Item.
*/
virtual Item* clone(StringPool* newPool) const override = 0;
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ee058aa..94042e3 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -115,6 +115,18 @@
};
/**
+ * Specialization that checks if the value is an Item.
+ */
+template <>
+struct DynCastVisitor<Item> : public RawValueVisitor {
+ Item* value = nullptr;
+
+ void visitItem(Item* item) override {
+ value = item;
+ }
+};
+
+/**
* Returns a valid pointer to T if the Value is of subtype T.
* Otherwise, returns nullptr.
*/
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp
index 1b9499d..cff935c 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/XmlPullParser.cpp
@@ -97,7 +97,7 @@
}
const std::u16string& XmlPullParser::getComment() const {
- return mEventQueue.front().comment;
+ return mEventQueue.front().data1;
}
size_t XmlPullParser::getLineNumber() const {
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
index f7d7a03..a0ce21d 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/XmlPullParser.h
@@ -158,7 +158,6 @@
size_t depth;
std::u16string data1;
std::u16string data2;
- std::u16string comment;
std::vector<Attribute> attributes;
};
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 095552a..47fa2a6 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -292,7 +292,7 @@
SymbolWriter* mSymbols;
StringPool* mSourcePool;
- template <typename T>
+ template <typename T, bool IsItem>
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
static_assert(std::is_same<ResTable_entry, T>::value ||
std::is_same<ResTable_entry_ext, T>::value,
@@ -308,7 +308,7 @@
outEntry->flags |= ResTable_entry::FLAG_WEAK;
}
- if (!entry->value->isItem()) {
+ if (!IsItem) {
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
}
@@ -329,8 +329,8 @@
}
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
- if (entry->value->isItem()) {
- writeEntry<ResTable_entry>(entry, buffer);
+ if (Item* item = valueCast<Item>(entry->value)) {
+ writeEntry<ResTable_entry, true>(entry, buffer);
if (Reference* ref = valueCast<Reference>(entry->value)) {
if (!ref->id) {
assert(ref->name && "reference must have at least a name");
@@ -339,12 +339,12 @@
}
}
Res_value* outValue = buffer->nextBlock<Res_value>();
- bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+ bool result = item->flatten(outValue);
assert(result && "flatten failed");
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
const size_t beforeEntry = buffer->size();
- ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+ ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
MapFlattenVisitor visitor(mSymbols, entry, buffer);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
@@ -551,17 +551,27 @@
// configuration available. Here we reverse this to match the binary table.
std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
for (ResourceEntry* entry : sortedEntries) {
- const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+ const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
// Group values by configuration.
for (auto& configValue : entry->values) {
- configToEntryListMap[configValue.config].push_back(FlatEntry{
- entry, configValue.value.get(), (uint32_t) keyIndex,
- (uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
- configValue.source.path)).getIndex()),
- (uint32_t)(configValue.source.line
- ? configValue.source.line.value() : 0)
- });
+ Value* value = configValue.value.get();
+
+ const StringPool::Ref sourceRef = mSourcePool->makeRef(
+ util::utf8ToUtf16(value->getSource().path));
+
+ uint32_t lineNumber = 0;
+ if (value->getSource().line) {
+ lineNumber = value->getSource().line.value();
+ }
+
+ configToEntryListMap[configValue.config]
+ .push_back(FlatEntry{
+ entry,
+ value,
+ keyIndex,
+ (uint32_t) sourceRef.getIndex(),
+ lineNumber });
}
}
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 0ccafc2..11fcc5d 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -20,21 +20,18 @@
#include "ValueVisitor.h"
#include "link/Linkers.h"
+#include "util/Comparators.h"
#include <algorithm>
#include <cassert>
namespace aapt {
-static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
- return lhs.config < config;
-}
-
bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
const int sdkVersionToGenerate) {
assert(sdkVersionToGenerate > config.sdkVersion);
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
// The source config came from this list, so it should be here.
assert(iter != entry->values.end());
@@ -107,21 +104,16 @@
// We found attributes from a higher SDK level. Check that
// there is no other defined resource for the version we want to
// generate.
- if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+ if (shouldGenerateVersionedResource(entry.get(),
+ configValue.config,
minSdkStripped.value())) {
// Let's create a new Style for this versioned resource.
ConfigDescription newConfig(configValue.config);
newConfig.sdkVersion = minSdkStripped.value();
- ResourceConfigValue newValue = {
- newConfig,
- configValue.source,
- configValue.comment,
- std::unique_ptr<Value>(configValue.value->clone(
- &table->stringPool))
- };
-
- Style* newStyle = static_cast<Style*>(newValue.value.get());
+ std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
+ newStyle->setComment(style->getComment());
+ newStyle->setSource(style->getSource());
// Move the previously stripped attributes into this style.
newStyle->entries.insert(newStyle->entries.end(),
@@ -130,9 +122,13 @@
// Insert the new Resource into the correct place.
auto iter = std::lower_bound(entry->values.begin(),
- entry->values.end(), newConfig,
- cmpConfigValue);
- entry->values.insert(iter, std::move(newValue));
+ entry->values.end(),
+ newConfig,
+ cmp::lessThan);
+
+ entry->values.insert(
+ iter,
+ ResourceConfigValue{ newConfig, std::move(newStyle) });
}
}
}
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index b84f2e0..ad701de 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -266,13 +266,14 @@
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
for (const auto& configValue : entry->values) {
- mContext.getDiagnostics()->error(DiagMessage(configValue.source)
- << "defined resource '"
- << ResourceNameRef(package->name,
- type->type,
- entry->name)
- << "' for external package '"
- << package->name << "'");
+ mContext.getDiagnostics()->error(
+ DiagMessage(configValue.value->getSource())
+ << "defined resource '"
+ << ResourceNameRef(package->name,
+ type->type,
+ entry->name)
+ << "' for external package '"
+ << package->name << "'");
error = true;
}
}
@@ -472,10 +473,11 @@
Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
exportedSymbol.name);
- if (!mergedTable.addResource(
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(f.source.withLine(exportedSymbol.line));
+ if (!mergedTable.addResourceAllowMangled(
mangledName ? mangledName.value() : exportedSymbol.name,
- {}, {}, f.source.withLine(exportedSymbol.line),
- util::make_unique<Id>(), mContext.getDiagnostics())) {
+ {}, std::move(id), mContext.getDiagnostics())) {
error = true;
}
}
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index a2f8d19..dbe0c92 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,13 +30,9 @@
.addSimple(u"@android:attr/privateA")
.addSimple(u"@android:attr/publicB")
.addSimple(u"@android:attr/privateB")
+ .setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
+ .setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
.build();
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
- ResourceId(0x01010000), {}, SymbolState::kPublic,
- context->getDiagnostics()));
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
- ResourceId(0x01010002), {}, SymbolState::kPublic,
- context->getDiagnostics()));
PrivateAttributeMover mover;
ASSERT_TRUE(mover.consume(context.get(), table.get()));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index b4fb996..8c924b5 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -206,13 +206,13 @@
if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
// The actual type of this item is incompatible with the attribute.
- DiagMessage msg;
+ DiagMessage msg(style->getSource());
buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
mContext->getDiagnostics()->error(msg);
mError = true;
}
} else {
- DiagMessage msg;
+ DiagMessage msg(style->getSource());
msg << "style attribute '";
if (entry.key.name) {
msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index db52546..636c2ba 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -19,6 +19,7 @@
#include "ValueVisitor.h"
#include "link/TableMerger.h"
+#include "util/Comparators.h"
#include "util/Util.h"
#include <cassert>
@@ -120,27 +121,24 @@
}
for (ResourceConfigValue& srcValue : srcEntry->values) {
- auto cmp = [](const ResourceConfigValue& a,
- const ConfigDescription& b) -> bool {
- return a.config < b;
- };
-
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
- srcValue.config, cmp);
+ srcValue.config, cmp::lessThan);
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
const int collisionResult = ResourceTable::resolveValueCollision(
iter->value.get(), srcValue.value.get());
if (collisionResult == 0) {
// Error!
- ResourceNameRef resourceName =
- { srcPackage->name, srcType->type, srcEntry->name };
- mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+ ResourceNameRef resourceName(srcPackage->name,
+ srcType->type,
+ srcEntry->name);
+
+ mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
<< "resource '" << resourceName
<< "' has a conflicting value for "
<< "configuration ("
<< srcValue.config << ")");
- mContext->getDiagnostics()->note(DiagMessage(iter->source)
+ mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
<< "originally defined here");
error = true;
continue;
@@ -150,16 +148,12 @@
}
} else {
- // Insert a new value.
- iter = dstEntry->values.insert(iter,
- ResourceConfigValue{ srcValue.config });
+ // Insert a place holder value. We will fill it in below.
+ iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
}
- iter->source = std::move(srcValue.source);
- iter->comment = std::move(srcValue.comment);
if (manglePackage) {
- iter->value = cloneAndMangle(srcTable, srcPackage->name,
- srcValue.value.get());
+ iter->value = cloneAndMangle(srcTable, srcPackage->name, srcValue.value.get());
} else {
iter->value = clone(srcValue.value.get());
}
@@ -179,7 +173,11 @@
std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
- return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ mMasterTable->stringPool.makeRef(newPath));
+ fileRef->setComment(f->getComment());
+ fileRef->setSource(f->getSource());
+ return std::move(fileRef);
}
}
return clone(value);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index c96b080..7309396 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -16,9 +16,11 @@
#include "ConfigDescription.h"
#include "Resource.h"
-#include "util/Util.h"
+#include "ValueVisitor.h"
#include "process/SymbolTable.h"
+#include "util/Comparators.h"
+#include "util/Util.h"
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
@@ -34,7 +36,7 @@
if (!result) {
if (name.type == ResourceType::kAttr) {
// Recurse and try looking up a private attribute.
- return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
+ return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
}
return {};
}
@@ -48,28 +50,26 @@
}
std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
- symbol->id = ResourceId{
- sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
+ symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
- auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
- return lhs.config < rhs;
- };
-
const ConfigDescription kDefaultConfig;
auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
- kDefaultConfig, lt);
+ kDefaultConfig, cmp::lessThan);
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
- symbol->attribute = util::make_unique<Attribute>(
- *static_cast<Attribute*>(iter->value.get()));
+ if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
+ symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+ } else {
+ return {};
+ }
}
}
if (name.type == ResourceType::kAttrPrivate) {
// Masquerade this entry as kAttr.
- mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
+ mCache.put(ResourceName(name.package, ResourceType::kAttr, name.entry), symbol);
} else {
mCache.put(name, symbol);
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0383c44..1b510e7 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -43,7 +43,7 @@
return *this;
}
- ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
+ ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) {
return addValue(name, id, util::make_unique<Id>());
}
@@ -51,7 +51,7 @@
return addReference(name, {}, ref);
}
- ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& ref) {
return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
}
@@ -60,7 +60,7 @@
return addString(name, {}, str);
}
- ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
const StringPiece16& str) {
return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
}
@@ -69,31 +69,43 @@
return addFileReference(name, {}, path);
}
- ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& path) {
return addValue(name, id,
util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
+ ResourceTableBuilder& addValue(const StringPiece16& name,
+ std::unique_ptr<Value> value) {
return addValue(name, {}, std::move(value));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
std::unique_ptr<Value> value) {
return addValue(name, id, {}, std::move(value));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
- const ConfigDescription& config, std::unique_ptr<Value> value) {
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
+ const ConfigDescription& config,
+ std::unique_ptr<Value> value) {
ResourceName resName = parseNameOrDie(name);
- bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+ bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
&mDiagnostics);
assert(result);
return *this;
}
+ ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id,
+ SymbolState state) {
+ ResourceName resName = parseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
+ assert(result);
+ return *this;
+ }
+
std::unique_ptr<ResourceTable> build() {
return std::move(mTable);
}
@@ -106,6 +118,32 @@
return reference;
}
+template <typename T>
+class ValueBuilder {
+private:
+ std::unique_ptr<Value> mValue;
+
+public:
+ template <typename... Args>
+ ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
+ }
+
+ template <typename... Args>
+ ValueBuilder& setSource(Args&&... args) {
+ mValue->setSource(Source{ std::forward<Args>(args)... });
+ return *this;
+ }
+
+ ValueBuilder& setComment(const StringPiece16& str) {
+ mValue->setComment(str);
+ return *this;
+ }
+
+ std::unique_ptr<Value> build() {
+ return std::move(mValue);
+ }
+};
+
class AttributeBuilder {
private:
std::unique_ptr<Attribute> mAttr;
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index c7a715e..314c1e8 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -422,26 +422,24 @@
const ResourceName name(package->name, *parsedType,
util::getString(mKeyPool, entry->key.index).toString());
- Source source;
+ Symbol symbol;
if (mSourcePool.getError() == NO_ERROR) {
- source.path = util::utf16ToUtf8(util::getString(
+ symbol.source.path = util::utf16ToUtf8(util::getString(
mSourcePool, util::deviceToHost32(entry->source.index)));
- source.line = util::deviceToHost32(entry->sourceLine);
+ symbol.source.line = util::deviceToHost32(entry->sourceLine);
}
- SymbolState state = SymbolState::kUndefined;
switch (util::deviceToHost16(entry->state)) {
case Public_entry::kPrivate:
- state = SymbolState::kPrivate;
+ symbol.state = SymbolState::kPrivate;
break;
case Public_entry::kPublic:
- state = SymbolState::kPublic;
+ symbol.state = SymbolState::kPublic;
break;
}
- if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
- mContext->getDiagnostics())) {
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
return false;
}
@@ -570,14 +568,17 @@
source.line = util::deviceToHost32(sourceBlock->line);
}
- if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
+ resourceValue->setSource(source);
+ if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
mContext->getDiagnostics())) {
return false;
}
if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
- SymbolState::kPublic,
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = mSource.withLine(0);
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
mContext->getDiagnostics())) {
return false;
}
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 4dbef5d..02c4081 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -66,26 +66,30 @@
bool parseTypeSpec(const android::ResChunk_header* chunk);
bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
- std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
- const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
+ std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::Res_value* value, uint16_t flags);
std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
IAaptContext* mContext;
ResourceTable* mTable;
diff --git a/tools/aapt2/util/Comparators.h b/tools/aapt2/util/Comparators.h
new file mode 100644
index 0000000..652018e
--- /dev/null
+++ b/tools/aapt2/util/Comparators.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_COMPARATORS_H
+#define AAPT_UTIL_COMPARATORS_H
+
+namespace aapt {
+namespace cmp {
+
+inline bool lessThan(const ResourceConfigValue& a, const ConfigDescription& b) {
+ return a.config < b;
+}
+
+} // namespace cmp
+} // namespace aapt
+
+#endif /* AAPT_UTIL_COMPARATORS_H */