Merge "Expose setDrawGLFunctionDetachedCallback to webview" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index b656945..1b331ac 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -71,6 +71,7 @@
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+ field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
@@ -9916,6 +9917,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
field public static final int PROTECTION_MASK_BASE = 15; // 0xf
@@ -19810,7 +19812,7 @@
public static abstract class AudioManager.AudioRecordingCallback {
ctor public AudioManager.AudioRecordingCallback();
- method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]);
+ method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]);
}
public static abstract interface AudioManager.OnAudioFocusChangeListener {
@@ -20579,12 +20581,13 @@
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
- field public static final int DolbyVisionProfileDvavDen = 2; // 0x2
- field public static final int DolbyVisionProfileDvavDer = 1; // 0x1
- field public static final int DolbyVisionProfileDvheDen = 4; // 0x4
- field public static final int DolbyVisionProfileDvheDer = 3; // 0x3
- field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5
- field public static final int DolbyVisionProfileDvheStn = 6; // 0x6
+ field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
+ field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
+ field public static final int DolbyVisionProfileDvheDen = 8; // 0x8
+ field public static final int DolbyVisionProfileDvheDer = 4; // 0x4
+ field public static final int DolbyVisionProfileDvheDth = 64; // 0x40
+ field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10
+ field public static final int DolbyVisionProfileDvheStn = 32; // 0x20
field public static final int H263Level10 = 1; // 0x1
field public static final int H263Level20 = 2; // 0x2
field public static final int H263Level30 = 4; // 0x4
@@ -44257,6 +44260,7 @@
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
+ method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
method public void getBoundsInScreen(android.graphics.Rect);
method public android.view.accessibility.AccessibilityWindowInfo getChild(int);
method public int getChildCount();
@@ -44264,6 +44268,7 @@
method public int getLayer();
method public android.view.accessibility.AccessibilityWindowInfo getParent();
method public android.view.accessibility.AccessibilityNodeInfo getRoot();
+ method public java.lang.CharSequence getTitle();
method public int getType();
method public boolean isAccessibilityFocused();
method public boolean isActive();
@@ -50005,6 +50010,7 @@
method public static int compare(boolean, boolean);
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
+ method public static int hashCode(boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -50022,6 +50028,7 @@
method public static java.lang.Byte decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(byte);
method public int intValue();
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -50030,6 +50037,7 @@
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Byte valueOf(java.lang.String) throws java.lang.NumberFormatException;
+ field public static final int BYTES = 1; // 0x1
field public static final byte MAX_VALUE = 127; // 0x7f
field public static final byte MIN_VALUE = -128; // 0xffffff80
field public static final int SIZE = 8; // 0x8
@@ -50067,6 +50075,7 @@
method public static int getNumericValue(int);
method public static int getType(char);
method public static int getType(int);
+ method public static int hashCode(char);
method public static char highSurrogate(int);
method public static boolean isAlphabetic(int);
method public static boolean isBmpCodePoint(int);
@@ -50127,6 +50136,7 @@
method public static char toUpperCase(char);
method public static int toUpperCase(int);
method public static java.lang.Character valueOf(char);
+ field public static final int BYTES = 2; // 0x2
field public static final byte COMBINING_SPACING_MARK = 8; // 0x8
field public static final byte CONNECTOR_PUNCTUATION = 23; // 0x17
field public static final byte CONTROL = 15; // 0xf
@@ -50753,6 +50763,7 @@
method public static int floatToIntBits(float);
method public static int floatToRawIntBits(float);
method public float floatValue();
+ method public static int hashCode(float);
method public static float intBitsToFloat(int);
method public int intValue();
method public static boolean isFinite(float);
@@ -50761,11 +50772,15 @@
method public static boolean isNaN(float);
method public boolean isNaN();
method public long longValue();
+ method public static float max(float, float);
+ method public static float min(float, float);
method public static float parseFloat(java.lang.String) throws java.lang.NumberFormatException;
+ method public static float sum(float, float);
method public static java.lang.String toHexString(float);
method public static java.lang.String toString(float);
method public static java.lang.Float valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Float valueOf(float);
+ field public static final int BYTES = 4; // 0x4
field public static final int MAX_EXPONENT = 127; // 0x7f
field public static final float MAX_VALUE = 3.4028235E38f;
field public static final int MIN_EXPONENT = -126; // 0xffffff82
@@ -50945,6 +50960,7 @@
method public static java.lang.Long valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(long);
+ field public static final int BYTES = 8; // 0x8
field public static final long MAX_VALUE = 9223372036854775807L; // 0x7fffffffffffffffL
field public static final long MIN_VALUE = -9223372036854775808L; // 0x8000000000000000L
field public static final int SIZE = 64; // 0x40
@@ -51248,6 +51264,7 @@
method public static java.lang.Short decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(short);
method public int intValue();
method public long longValue();
method public static short parseShort(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51257,6 +51274,7 @@
method public static java.lang.Short valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(short);
+ field public static final int BYTES = 2; // 0x2
field public static final short MAX_VALUE = 32767; // 0x7fff
field public static final short MIN_VALUE = -32768; // 0xffff8000
field public static final int SIZE = 16; // 0x10
diff --git a/api/system-current.txt b/api/system-current.txt
index 5ae1bf6..f50602d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+ field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED";
field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
@@ -10315,6 +10316,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
field public static final int PROTECTION_MASK_BASE = 15; // 0xf
@@ -15269,6 +15271,7 @@
ctor public ContextHubInfo();
method public int describeContents();
method public int getId();
+ method public int getMaxPacketLengthBytes();
method public android.hardware.location.MemoryRegion[] getMemoryRegions();
method public java.lang.String getName();
method public float getPeakMips();
@@ -15296,10 +15299,6 @@
method public int sendMessage(int, int, android.hardware.location.ContextHubMessage);
method public int unloadNanoApp(int);
method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback);
- field public static final int ANY_HUB = -1; // 0xffffffff
- field public static final int MSG_DATA_SEND = 3; // 0x3
- field public static final int MSG_LOAD_NANO_APP = 1; // 0x1
- field public static final int MSG_UNLOAD_NANO_APP = 2; // 0x2
}
public static abstract class ContextHubManager.Callback {
@@ -15493,7 +15492,7 @@
public class NanoAppInstanceInfo {
ctor public NanoAppInstanceInfo();
method public int describeContents();
- method public int getAppId();
+ method public long getAppId();
method public int getAppVersion();
method public int getContexthubId();
method public int getHandle();
@@ -15504,17 +15503,6 @@
method public int getNeededWriteMemBytes();
method public int[] getOutputEvents();
method public java.lang.String getPublisher();
- method public void setAppId(int);
- method public void setAppVersion(int);
- method public void setContexthubId(int);
- method public void setHandle(int);
- method public void setName(java.lang.String);
- method public void setNeededExecMemBytes(int);
- method public void setNeededReadMemBytes(int);
- method public void setNeededSensors(int[]);
- method public void setNeededWriteMemBytes(int);
- method public void setOutputEvents(int[]);
- method public void setPublisher(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR;
}
@@ -21300,7 +21288,7 @@
public static abstract class AudioManager.AudioRecordingCallback {
ctor public AudioManager.AudioRecordingCallback();
- method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]);
+ method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]);
}
public static abstract interface AudioManager.OnAudioFocusChangeListener {
@@ -22072,12 +22060,13 @@
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
- field public static final int DolbyVisionProfileDvavDen = 2; // 0x2
- field public static final int DolbyVisionProfileDvavDer = 1; // 0x1
- field public static final int DolbyVisionProfileDvheDen = 4; // 0x4
- field public static final int DolbyVisionProfileDvheDer = 3; // 0x3
- field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5
- field public static final int DolbyVisionProfileDvheStn = 6; // 0x6
+ field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
+ field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
+ field public static final int DolbyVisionProfileDvheDen = 8; // 0x8
+ field public static final int DolbyVisionProfileDvheDer = 4; // 0x4
+ field public static final int DolbyVisionProfileDvheDth = 64; // 0x40
+ field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10
+ field public static final int DolbyVisionProfileDvheStn = 32; // 0x20
field public static final int H263Level10 = 1; // 0x1
field public static final int H263Level20 = 2; // 0x2
field public static final int H263Level30 = 4; // 0x4
@@ -24629,6 +24618,7 @@
method public android.media.tv.TvInputInfo.Builder setHdmiDeviceInfo(android.hardware.hdmi.HdmiDeviceInfo);
method public android.media.tv.TvInputInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.media.tv.TvInputInfo.Builder setIcon(android.graphics.drawable.Icon, int);
+ method public android.media.tv.TvInputInfo.Builder setLabel(java.lang.CharSequence);
method public android.media.tv.TvInputInfo.Builder setLabel(int);
method public android.media.tv.TvInputInfo.Builder setParentId(java.lang.String);
method public android.media.tv.TvInputInfo.Builder setTunerCount(int);
@@ -47013,6 +47003,7 @@
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
+ method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
method public void getBoundsInScreen(android.graphics.Rect);
method public android.view.accessibility.AccessibilityWindowInfo getChild(int);
method public int getChildCount();
@@ -47020,6 +47011,7 @@
method public int getLayer();
method public android.view.accessibility.AccessibilityWindowInfo getParent();
method public android.view.accessibility.AccessibilityNodeInfo getRoot();
+ method public java.lang.CharSequence getTitle();
method public int getType();
method public boolean isAccessibilityFocused();
method public boolean isActive();
@@ -53115,6 +53107,7 @@
method public static int compare(boolean, boolean);
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
+ method public static int hashCode(boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -53132,6 +53125,7 @@
method public static java.lang.Byte decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(byte);
method public int intValue();
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -53140,6 +53134,7 @@
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Byte valueOf(java.lang.String) throws java.lang.NumberFormatException;
+ field public static final int BYTES = 1; // 0x1
field public static final byte MAX_VALUE = 127; // 0x7f
field public static final byte MIN_VALUE = -128; // 0xffffff80
field public static final int SIZE = 8; // 0x8
@@ -53177,6 +53172,7 @@
method public static int getNumericValue(int);
method public static int getType(char);
method public static int getType(int);
+ method public static int hashCode(char);
method public static char highSurrogate(int);
method public static boolean isAlphabetic(int);
method public static boolean isBmpCodePoint(int);
@@ -53237,6 +53233,7 @@
method public static char toUpperCase(char);
method public static int toUpperCase(int);
method public static java.lang.Character valueOf(char);
+ field public static final int BYTES = 2; // 0x2
field public static final byte COMBINING_SPACING_MARK = 8; // 0x8
field public static final byte CONNECTOR_PUNCTUATION = 23; // 0x17
field public static final byte CONTROL = 15; // 0xf
@@ -53863,6 +53860,7 @@
method public static int floatToIntBits(float);
method public static int floatToRawIntBits(float);
method public float floatValue();
+ method public static int hashCode(float);
method public static float intBitsToFloat(int);
method public int intValue();
method public static boolean isFinite(float);
@@ -53871,11 +53869,15 @@
method public static boolean isNaN(float);
method public boolean isNaN();
method public long longValue();
+ method public static float max(float, float);
+ method public static float min(float, float);
method public static float parseFloat(java.lang.String) throws java.lang.NumberFormatException;
+ method public static float sum(float, float);
method public static java.lang.String toHexString(float);
method public static java.lang.String toString(float);
method public static java.lang.Float valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Float valueOf(float);
+ field public static final int BYTES = 4; // 0x4
field public static final int MAX_EXPONENT = 127; // 0x7f
field public static final float MAX_VALUE = 3.4028235E38f;
field public static final int MIN_EXPONENT = -126; // 0xffffff82
@@ -54055,6 +54057,7 @@
method public static java.lang.Long valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(long);
+ field public static final int BYTES = 8; // 0x8
field public static final long MAX_VALUE = 9223372036854775807L; // 0x7fffffffffffffffL
field public static final long MIN_VALUE = -9223372036854775808L; // 0x8000000000000000L
field public static final int SIZE = 64; // 0x40
@@ -54358,6 +54361,7 @@
method public static java.lang.Short decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(short);
method public int intValue();
method public long longValue();
method public static short parseShort(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -54367,6 +54371,7 @@
method public static java.lang.Short valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(short);
+ field public static final int BYTES = 2; // 0x2
field public static final short MAX_VALUE = 32767; // 0x7fff
field public static final short MIN_VALUE = -32768; // 0xffff8000
field public static final int SIZE = 16; // 0x10
diff --git a/api/test-current.txt b/api/test-current.txt
index 334a679..f60aab1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -71,6 +71,7 @@
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+ field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
@@ -9926,6 +9927,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
field public static final int PROTECTION_MASK_BASE = 15; // 0xf
@@ -19875,7 +19877,7 @@
public static abstract class AudioManager.AudioRecordingCallback {
ctor public AudioManager.AudioRecordingCallback();
- method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]);
+ method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]);
}
public static abstract interface AudioManager.OnAudioFocusChangeListener {
@@ -20644,12 +20646,13 @@
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
- field public static final int DolbyVisionProfileDvavDen = 2; // 0x2
- field public static final int DolbyVisionProfileDvavDer = 1; // 0x1
- field public static final int DolbyVisionProfileDvheDen = 4; // 0x4
- field public static final int DolbyVisionProfileDvheDer = 3; // 0x3
- field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5
- field public static final int DolbyVisionProfileDvheStn = 6; // 0x6
+ field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
+ field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
+ field public static final int DolbyVisionProfileDvheDen = 8; // 0x8
+ field public static final int DolbyVisionProfileDvheDer = 4; // 0x4
+ field public static final int DolbyVisionProfileDvheDth = 64; // 0x40
+ field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10
+ field public static final int DolbyVisionProfileDvheStn = 32; // 0x20
field public static final int H263Level10 = 1; // 0x1
field public static final int H263Level20 = 2; // 0x2
field public static final int H263Level30 = 4; // 0x4
@@ -44331,6 +44334,7 @@
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
+ method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
method public void getBoundsInScreen(android.graphics.Rect);
method public android.view.accessibility.AccessibilityWindowInfo getChild(int);
method public int getChildCount();
@@ -44338,6 +44342,7 @@
method public int getLayer();
method public android.view.accessibility.AccessibilityWindowInfo getParent();
method public android.view.accessibility.AccessibilityNodeInfo getRoot();
+ method public java.lang.CharSequence getTitle();
method public int getType();
method public boolean isAccessibilityFocused();
method public boolean isActive();
@@ -50079,6 +50084,7 @@
method public static int compare(boolean, boolean);
method public int compareTo(java.lang.Boolean);
method public static boolean getBoolean(java.lang.String);
+ method public static int hashCode(boolean);
method public static boolean parseBoolean(java.lang.String);
method public static java.lang.String toString(boolean);
method public static java.lang.Boolean valueOf(boolean);
@@ -50096,6 +50102,7 @@
method public static java.lang.Byte decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(byte);
method public int intValue();
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -50104,6 +50111,7 @@
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Byte valueOf(java.lang.String) throws java.lang.NumberFormatException;
+ field public static final int BYTES = 1; // 0x1
field public static final byte MAX_VALUE = 127; // 0x7f
field public static final byte MIN_VALUE = -128; // 0xffffff80
field public static final int SIZE = 8; // 0x8
@@ -50141,6 +50149,7 @@
method public static int getNumericValue(int);
method public static int getType(char);
method public static int getType(int);
+ method public static int hashCode(char);
method public static char highSurrogate(int);
method public static boolean isAlphabetic(int);
method public static boolean isBmpCodePoint(int);
@@ -50201,6 +50210,7 @@
method public static char toUpperCase(char);
method public static int toUpperCase(int);
method public static java.lang.Character valueOf(char);
+ field public static final int BYTES = 2; // 0x2
field public static final byte COMBINING_SPACING_MARK = 8; // 0x8
field public static final byte CONNECTOR_PUNCTUATION = 23; // 0x17
field public static final byte CONTROL = 15; // 0xf
@@ -50827,6 +50837,7 @@
method public static int floatToIntBits(float);
method public static int floatToRawIntBits(float);
method public float floatValue();
+ method public static int hashCode(float);
method public static float intBitsToFloat(int);
method public int intValue();
method public static boolean isFinite(float);
@@ -50835,11 +50846,15 @@
method public static boolean isNaN(float);
method public boolean isNaN();
method public long longValue();
+ method public static float max(float, float);
+ method public static float min(float, float);
method public static float parseFloat(java.lang.String) throws java.lang.NumberFormatException;
+ method public static float sum(float, float);
method public static java.lang.String toHexString(float);
method public static java.lang.String toString(float);
method public static java.lang.Float valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Float valueOf(float);
+ field public static final int BYTES = 4; // 0x4
field public static final int MAX_EXPONENT = 127; // 0x7f
field public static final float MAX_VALUE = 3.4028235E38f;
field public static final int MIN_EXPONENT = -126; // 0xffffff82
@@ -51019,6 +51034,7 @@
method public static java.lang.Long valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Long valueOf(long);
+ field public static final int BYTES = 8; // 0x8
field public static final long MAX_VALUE = 9223372036854775807L; // 0x7fffffffffffffffL
field public static final long MIN_VALUE = -9223372036854775808L; // 0x8000000000000000L
field public static final int SIZE = 64; // 0x40
@@ -51322,6 +51338,7 @@
method public static java.lang.Short decode(java.lang.String) throws java.lang.NumberFormatException;
method public double doubleValue();
method public float floatValue();
+ method public static int hashCode(short);
method public int intValue();
method public long longValue();
method public static short parseShort(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51331,6 +51348,7 @@
method public static java.lang.Short valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(java.lang.String) throws java.lang.NumberFormatException;
method public static java.lang.Short valueOf(short);
+ field public static final int BYTES = 2; // 0x2
field public static final short MAX_VALUE = 32767; // 0x7fff
field public static final short MIN_VALUE = -32768; // 0xffff8000
field public static final int SIZE = 16; // 0x10
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index b208e43..0e674c8 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -88,6 +88,8 @@
runForget();
} else if ("set-emulate-fbe".equals(op)) {
runSetEmulateFbe();
+ } else if ("get-fbe-mode".equals(op)) {
+ runGetFbeMode();
} else {
throw new IllegalArgumentException();
}
@@ -145,6 +147,16 @@
StorageManager.DEBUG_EMULATE_FBE);
}
+ public void runGetFbeMode() {
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ System.out.println("native");
+ } else if (StorageManager.isFileEncryptedEmulatedOnly()) {
+ System.out.println("emulated");
+ } else {
+ System.out.println("none");
+ }
+ }
+
public void runPartition() throws RemoteException {
final String diskId = nextArg();
final String type = nextArg();
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4019a56..ee03280 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -36,11 +36,11 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import com.android.internal.R;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -319,6 +319,9 @@
*/
public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040;
+ /** {@hide} */
+ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
+
/**
* The event types an {@link AccessibilityService} is interested in.
* <p>
@@ -687,8 +690,9 @@
}
/** {@hide} */
- public boolean isEncryptionAware() {
- return mResolveInfo.serviceInfo.directBootAware;
+ public boolean isDirectBootAware() {
+ return ((flags & FLAG_FORCE_DIRECT_BOOT_AWARE) != 0)
+ || mResolveInfo.serviceInfo.directBootAware;
}
/**
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index 7a0c89b..e18a34d 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -36,8 +36,7 @@
* Accessibility services with the
* {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
* gestures. This class describes those gestures. Gestures are made up of one or more strokes.
- * Gestures are immutable; use the {@code create} methods to get common gesture, or a
- * {@code Builder} to create a new one.
+ * Gestures are immutable once built.
* <p>
* Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
*/
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index e520b40..7465ed9 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2798,6 +2798,15 @@
if (account == null) {
throw new IllegalArgumentException("account is null");
}
+
+ // Always include the calling package name. This just makes life easier
+ // down stream.
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
return new AmsTask(activity, handler, callback) {
@Override
public void doWork() throws RemoteException {
@@ -2806,7 +2815,7 @@
account,
authTokenType,
activity != null,
- options);
+ optionsIn);
}
}.start();
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 33621852..65d48e6 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -95,7 +95,7 @@
}
return sSystemReady;
}
- static boolean sSystemReady = false;
+ static volatile boolean sSystemReady = false;
static public void broadcastStickyIntent(Intent intent, String permission, int userId) {
broadcastStickyIntent(intent, permission, AppOpsManager.OP_NONE, userId);
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index d3ca7ee..4a1aff7 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -239,9 +239,6 @@
public void onResume(Activity activity, boolean isTopOfTask) {
// After orientation change, the onResume can come in before the top Activity has
// left, so if the Activity is not top, wait a second for the top Activity to exit.
- if (mCalledExitCoordinator == null) {
- return; // This is the called activity
- }
if (isTopOfTask || mEnterTransitionCoordinator == null) {
restoreExitedViews();
restoreReenteringViews();
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index cb2130c4..e4fff9d 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -905,10 +905,12 @@
ListenerWrapper wrapper = null;
synchronized (AlarmManager.class) {
- final WeakReference<ListenerWrapper> wrapperRef;
- wrapperRef = sWrappers.get(listener);
- if (wrapperRef != null) {
- wrapper = wrapperRef.get();
+ if (sWrappers != null) {
+ final WeakReference<ListenerWrapper> wrapperRef;
+ wrapperRef = sWrappers.get(listener);
+ if (wrapperRef != null) {
+ wrapper = wrapperRef.get();
+ }
}
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 83dc506..bd55a06 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -58,7 +58,7 @@
* @param context the parent context
*/
public DatePickerDialog(@NonNull Context context) {
- this(context, 0);
+ this(context, 0, null, Calendar.getInstance(), -1, -1, -1);
}
/**
@@ -70,24 +70,7 @@
* {@code context}'s default alert dialog theme
*/
public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId) {
- super(context, resolveDialogTheme(context, themeResId));
-
- final Context themeContext = getContext();
- final LayoutInflater inflater = LayoutInflater.from(themeContext);
- final View view = inflater.inflate(R.layout.date_picker_dialog, null);
- setView(view);
-
- setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
- setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
- setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
-
- final Calendar calendar = Calendar.getInstance();
- final int year = calendar.get(Calendar.YEAR);
- final int monthOfYear = calendar.get(Calendar.MONTH);
- final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
- mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
- mDatePicker.init(year, monthOfYear, dayOfMonth, this);
- mDatePicker.setValidationCallback(mValidationCallback);
+ this(context, themeResId, null, Calendar.getInstance(), -1, -1, -1);
}
/**
@@ -104,7 +87,7 @@
*/
public DatePickerDialog(@NonNull Context context, @Nullable OnDateSetListener listener,
int year, int month, int dayOfMonth) {
- this(context, 0, listener, year, month, dayOfMonth);
+ this(context, 0, listener, null, year, month, dayOfMonth);
}
/**
@@ -116,16 +99,40 @@
* {@code context}'s default alert dialog theme
* @param listener the listener to call when the user sets the date
* @param year the initially selected year
- * @param month the initially selected month (0-11 for compatibility with
- * {@link Calendar#MONTH})
+ * @param monthOfYear the initially selected month of the year (0-11 for
+ * compatibility with {@link Calendar#MONTH})
* @param dayOfMonth the initially selected day of month (1-31, depending
* on month)
*/
public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
- @Nullable OnDateSetListener listener, int year, int month, int dayOfMonth) {
- this(context, themeResId);
+ @Nullable OnDateSetListener listener, int year, int monthOfYear, int dayOfMonth) {
+ this(context, themeResId, listener, null, year, monthOfYear, dayOfMonth);
+ }
- mDatePicker.updateDate(year, month, dayOfMonth);
+ private DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
+ @Nullable OnDateSetListener listener, @Nullable Calendar calendar, int year,
+ int monthOfYear, int dayOfMonth) {
+ super(context, resolveDialogTheme(context, themeResId));
+
+ final Context themeContext = getContext();
+ final LayoutInflater inflater = LayoutInflater.from(themeContext);
+ final View view = inflater.inflate(R.layout.date_picker_dialog, null);
+ setView(view);
+
+ setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
+ setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
+ setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
+
+ if (calendar != null) {
+ year = calendar.get(Calendar.YEAR);
+ monthOfYear = calendar.get(Calendar.MONTH);
+ dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
+ mDatePicker.init(year, monthOfYear, dayOfMonth, this);
+ mDatePicker.setValidationCallback(mValidationCallback);
+
mDateSetListener = listener;
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 0bb1097..85a0403 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,6 +16,10 @@
package android.app;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -43,7 +47,6 @@
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.KeyboardShortcutGroup;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -57,12 +60,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PhoneWindow;
-
import java.lang.ref.WeakReference;
-import java.util.List;
/**
* Base class for Dialogs.
@@ -94,11 +92,14 @@
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
private static final String TAG = "Dialog";
private Activity mOwnerActivity;
-
+
+ private final WindowManager mWindowManager;
+
final Context mContext;
- final WindowManager mWindowManager;
- Window mWindow;
+ final Window mWindow;
+
View mDecor;
+
private ActionBar mActionBar;
/**
* This field should be made private, so it is hidden from the SDK.
@@ -123,7 +124,7 @@
private static final int CANCEL = 0x44;
private static final int SHOW = 0x45;
- private Handler mListenersHandler;
+ private final Handler mListenersHandler;
private SearchEvent mSearchEvent;
@@ -131,11 +132,7 @@
private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
- private final Runnable mDismissAction = new Runnable() {
- public void run() {
- dismissDialog();
- }
- };
+ private final Runnable mDismissAction = this::dismissDialog;
/**
* Creates a dialog window that uses the default dialog theme.
@@ -198,14 +195,15 @@
* @hide
*/
@Deprecated
- protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) {
+ protected Dialog(@NonNull Context context, boolean cancelable,
+ @Nullable Message cancelCallback) {
this(context);
mCancelable = cancelable;
mCancelMessage = cancelCallback;
}
protected Dialog(@NonNull Context context, boolean cancelable,
- OnCancelListener cancelListener) {
+ @Nullable OnCancelListener cancelListener) {
this(context);
mCancelable = cancelable;
setOnCancelListener(cancelListener);
@@ -216,8 +214,7 @@
*
* @return Context The Context used by the Dialog.
*/
- @NonNull
- public final Context getContext() {
+ public final @NonNull Context getContext() {
return mContext;
}
@@ -226,7 +223,7 @@
*
* @return The ActionBar attached to the dialog or null if no ActionBar is present.
*/
- public ActionBar getActionBar() {
+ public @Nullable ActionBar getActionBar() {
return mActionBar;
}
@@ -236,7 +233,7 @@
*
* @param activity The Activity that owns this dialog.
*/
- public final void setOwnerActivity(Activity activity) {
+ public final void setOwnerActivity(@NonNull Activity activity) {
mOwnerActivity = activity;
getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream());
@@ -250,7 +247,7 @@
*
* @return The Activity that owns this Dialog.
*/
- public final Activity getOwnerActivity() {
+ public final @Nullable Activity getOwnerActivity() {
return mOwnerActivity;
}
@@ -316,13 +313,10 @@
l = nl;
}
- try {
- mWindowManager.addView(mDecor, l);
- mShowing = true;
-
- sendShowMessage();
- } finally {
- }
+ mWindowManager.addView(mDecor, l);
+ mShowing = true;
+
+ sendShowMessage();
}
/**
@@ -388,7 +382,7 @@
}
}
- // internal method to make sure mcreated is set properly without requiring
+ // internal method to make sure mCreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
@@ -400,7 +394,7 @@
/**
* Similar to {@link Activity#onCreate}, you should initialize your dialog
* in this method, including calling {@link #setContentView}.
- * @param savedInstanceState If this dialog is being reinitalized after a
+ * @param savedInstanceState If this dialog is being reinitialized after a
* the hosting activity was previously shut down, holds the result from
* the most recent call to {@link #onSaveInstanceState}, or null if this
* is the first time.
@@ -433,7 +427,7 @@
* state.
* @return A bundle with the state of the dialog.
*/
- public Bundle onSaveInstanceState() {
+ public @NonNull Bundle onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
if (mCreated) {
@@ -452,7 +446,7 @@
* @param savedInstanceState The state of the dialog previously saved by
* {@link #onSaveInstanceState()}.
*/
- public void onRestoreInstanceState(Bundle savedInstanceState) {
+ public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
if (dialogHierarchyState == null) {
// dialog has never been shown, or onCreated, nothing to restore.
@@ -473,7 +467,7 @@
* @return Window The current window, or null if the activity is not
* visual.
*/
- public Window getWindow() {
+ public @Nullable Window getWindow() {
return mWindow;
}
@@ -486,7 +480,7 @@
* @see #getWindow
* @see android.view.Window#getCurrentFocus
*/
- public View getCurrentFocus() {
+ public @Nullable View getCurrentFocus() {
return mWindow != null ? mWindow.getCurrentFocus() : null;
}
@@ -498,8 +492,7 @@
* @param id the identifier of the view to find
* @return The view with the given id or null.
*/
- @Nullable
- public View findViewById(@IdRes int id) {
+ public @Nullable View findViewById(@IdRes int id) {
return mWindow.findViewById(id);
}
@@ -520,19 +513,19 @@
*
* @param view The desired content to display.
*/
- public void setContentView(View view) {
+ public void setContentView(@NonNull View view) {
mWindow.setContentView(view);
}
/**
* Set the screen content to an explicit view. This view is placed
* directly into the screen's view hierarchy. It can itself be a complex
- * view hierarhcy.
+ * view hierarchy.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
- public void setContentView(View view, ViewGroup.LayoutParams params) {
+ public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
mWindow.setContentView(view, params);
}
@@ -543,7 +536,7 @@
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
- public void addContentView(View view, ViewGroup.LayoutParams params) {
+ public void addContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
mWindow.addContentView(view, params);
}
@@ -552,7 +545,7 @@
*
* @param title The new text to display in the title.
*/
- public void setTitle(CharSequence title) {
+ public void setTitle(@Nullable CharSequence title) {
mWindow.setTitle(title);
mWindow.getAttributes().setTitle(title);
}
@@ -578,7 +571,8 @@
* @see #onKeyUp
* @see android.view.KeyEvent
*/
- public boolean onKeyDown(int keyCode, KeyEvent event) {
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
event.startTracking();
return true;
@@ -592,7 +586,8 @@
* KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
* the event).
*/
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ @Override
+ public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
return false;
}
@@ -605,7 +600,8 @@
* @see #onKeyDown
* @see KeyEvent
*/
- public boolean onKeyUp(int keyCode, KeyEvent event) {
+ @Override
+ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
@@ -619,7 +615,8 @@
* KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
* the event).
*/
- public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event) {
return false;
}
@@ -644,7 +641,7 @@
* @param event Description of the key event.
* @return True if the key shortcut was handled.
*/
- public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ public boolean onKeyShortcut(int keyCode, @NonNull KeyEvent event) {
return false;
}
@@ -658,7 +655,7 @@
* The default implementation will cancel the dialog when a touch
* happens outside of the window bounds.
*/
- public boolean onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
cancel();
return true;
@@ -681,7 +678,7 @@
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
- public boolean onTrackballEvent(MotionEvent event) {
+ public boolean onTrackballEvent(@NonNull MotionEvent event) {
return false;
}
@@ -710,25 +707,30 @@
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
- public boolean onGenericMotionEvent(MotionEvent event) {
+ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
return false;
}
+ @Override
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
if (mDecor != null) {
mWindowManager.updateViewLayout(mDecor, params);
}
}
+ @Override
public void onContentChanged() {
}
+ @Override
public void onWindowFocusChanged(boolean hasFocus) {
}
+ @Override
public void onAttachedToWindow() {
}
+ @Override
public void onDetachedFromWindow() {
}
@@ -747,7 +749,8 @@
*
* @return boolean Return true if this event was consumed.
*/
- public boolean dispatchKeyEvent(KeyEvent event) {
+ @Override
+ public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) {
return true;
}
@@ -767,7 +770,8 @@
* @param event The key shortcut event.
* @return True if this event was consumed.
*/
- public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ @Override
+ public boolean dispatchKeyShortcutEvent(@NonNull KeyEvent event) {
if (mWindow.superDispatchKeyShortcutEvent(event)) {
return true;
}
@@ -784,7 +788,8 @@
*
* @return boolean Return true if this event was consumed.
*/
- public boolean dispatchTouchEvent(MotionEvent ev) {
+ @Override
+ public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
if (mWindow.superDispatchTouchEvent(ev)) {
return true;
}
@@ -801,7 +806,8 @@
*
* @return boolean Return true if this event was consumed.
*/
- public boolean dispatchTrackballEvent(MotionEvent ev) {
+ @Override
+ public boolean dispatchTrackballEvent(@NonNull MotionEvent ev) {
if (mWindow.superDispatchTrackballEvent(ev)) {
return true;
}
@@ -818,14 +824,16 @@
*
* @return boolean Return true if this event was consumed.
*/
- public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ @Override
+ public boolean dispatchGenericMotionEvent(@NonNull MotionEvent ev) {
if (mWindow.superDispatchGenericMotionEvent(ev)) {
return true;
}
return onGenericMotionEvent(ev);
}
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) {
event.setClassName(getClass().getName());
event.setPackageName(mContext.getPackageName());
@@ -840,6 +848,7 @@
/**
* @see Activity#onCreatePanelView(int)
*/
+ @Override
public View onCreatePanelView(int featureId) {
return null;
}
@@ -847,7 +856,8 @@
/**
* @see Activity#onCreatePanelMenu(int, Menu)
*/
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ @Override
+ public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
if (featureId == Window.FEATURE_OPTIONS_PANEL) {
return onCreateOptionsMenu(menu);
}
@@ -858,10 +868,10 @@
/**
* @see Activity#onPreparePanel(int, View, Menu)
*/
+ @Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
- boolean goforit = onPrepareOptionsMenu(menu);
- return goforit && menu.hasVisibleItems();
+ return onPrepareOptionsMenu(menu) && menu.hasVisibleItems();
}
return true;
}
@@ -869,6 +879,7 @@
/**
* @see Activity#onMenuOpened(int, Menu)
*/
+ @Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (featureId == Window.FEATURE_ACTION_BAR) {
mActionBar.dispatchMenuVisibilityChanged(true);
@@ -879,6 +890,7 @@
/**
* @see Activity#onMenuItemSelected(int, MenuItem)
*/
+ @Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
return false;
}
@@ -886,6 +898,7 @@
/**
* @see Activity#onPanelClosed(int, Menu)
*/
+ @Override
public void onPanelClosed(int featureId, Menu menu) {
if (featureId == Window.FEATURE_ACTION_BAR) {
mActionBar.dispatchMenuVisibilityChanged(false);
@@ -900,7 +913,7 @@
* @see Activity#onCreateOptionsMenu(Menu)
* @see #getOwnerActivity()
*/
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(@NonNull Menu menu) {
return true;
}
@@ -912,21 +925,21 @@
* @see Activity#onPrepareOptionsMenu(Menu)
* @see #getOwnerActivity()
*/
- public boolean onPrepareOptionsMenu(Menu menu) {
+ public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
return true;
}
/**
* @see Activity#onOptionsItemSelected(MenuItem)
*/
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return false;
}
/**
* @see Activity#onOptionsMenuClosed(Menu)
*/
- public void onOptionsMenuClosed(Menu menu) {
+ public void onOptionsMenuClosed(@NonNull Menu menu) {
}
/**
@@ -959,47 +972,49 @@
/**
* @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
*/
+ @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
}
/**
* @see Activity#registerForContextMenu(View)
*/
- public void registerForContextMenu(View view) {
+ public void registerForContextMenu(@NonNull View view) {
view.setOnCreateContextMenuListener(this);
}
/**
* @see Activity#unregisterForContextMenu(View)
*/
- public void unregisterForContextMenu(View view) {
+ public void unregisterForContextMenu(@NonNull View view) {
view.setOnCreateContextMenuListener(null);
}
/**
* @see Activity#openContextMenu(View)
*/
- public void openContextMenu(View view) {
+ public void openContextMenu(@NonNull View view) {
view.showContextMenu();
}
/**
* @see Activity#onContextItemSelected(MenuItem)
*/
- public boolean onContextItemSelected(MenuItem item) {
+ public boolean onContextItemSelected(@NonNull MenuItem item) {
return false;
}
/**
* @see Activity#onContextMenuClosed(Menu)
*/
- public void onContextMenuClosed(Menu menu) {
+ public void onContextMenuClosed(@NonNull Menu menu) {
}
/**
* This hook is called when the user signals the desire to start a search.
*/
- public boolean onSearchRequested(SearchEvent searchEvent) {
+ @Override
+ public boolean onSearchRequested(@NonNull SearchEvent searchEvent) {
mSearchEvent = searchEvent;
return onSearchRequested();
}
@@ -1007,6 +1022,7 @@
/**
* This hook is called when the user signals the desire to start a search.
*/
+ @Override
public boolean onSearchRequested() {
final SearchManager searchManager = (SearchManager) mContext
.getSystemService(Context.SEARCH_SERVICE);
@@ -1029,13 +1045,10 @@
* @return SearchEvent The SearchEvent that triggered the {@link
* #onSearchRequested} callback.
*/
- public final SearchEvent getSearchEvent() {
+ public final @Nullable SearchEvent getSearchEvent() {
return mSearchEvent;
}
- /**
- * {@inheritDoc}
- */
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
if (mActionBar != null && mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
@@ -1044,9 +1057,6 @@
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
try {
@@ -1063,6 +1073,7 @@
* Note that if you override this method you should always call through
* to the superclass implementation by calling super.onActionModeStarted(mode).
*/
+ @Override
@CallSuper
public void onActionModeStarted(ActionMode mode) {
mActionMode = mode;
@@ -1074,6 +1085,7 @@
* Note that if you override this method you should always call through
* to the superclass implementation by calling super.onActionModeFinished(mode).
*/
+ @Override
@CallSuper
public void onActionModeFinished(ActionMode mode) {
if (mode == mActionMode) {
@@ -1139,7 +1151,7 @@
* Convenience for calling
* {@link android.view.Window#setFeatureDrawableUri}.
*/
- public final void setFeatureDrawableUri(int featureId, Uri uri) {
+ public final void setFeatureDrawableUri(int featureId, @Nullable Uri uri) {
getWindow().setFeatureDrawableUri(featureId, uri);
}
@@ -1147,7 +1159,7 @@
* Convenience for calling
* {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
*/
- public final void setFeatureDrawable(int featureId, Drawable drawable) {
+ public final void setFeatureDrawable(int featureId, @Nullable Drawable drawable) {
getWindow().setFeatureDrawable(featureId, drawable);
}
@@ -1159,7 +1171,7 @@
getWindow().setFeatureDrawableAlpha(featureId, alpha);
}
- public LayoutInflater getLayoutInflater() {
+ public @NonNull LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
@@ -1191,6 +1203,7 @@
* Cancel the dialog. This is essentially the same as calling {@link #dismiss()}, but it will
* also call your {@link DialogInterface.OnCancelListener} (if registered).
*/
+ @Override
public void cancel() {
if (!mCanceled && mCancelMessage != null) {
mCanceled = true;
@@ -1211,7 +1224,7 @@
*
* @param listener The {@link DialogInterface.OnCancelListener} to use.
*/
- public void setOnCancelListener(final OnCancelListener listener) {
+ public void setOnCancelListener(@Nullable OnCancelListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnCancelListener is already taken by "
@@ -1229,7 +1242,7 @@
* @param msg The msg to send when the dialog is canceled.
* @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
*/
- public void setCancelMessage(final Message msg) {
+ public void setCancelMessage(@Nullable Message msg) {
mCancelMessage = msg;
}
@@ -1237,7 +1250,7 @@
* Set a listener to be invoked when the dialog is dismissed.
* @param listener The {@link DialogInterface.OnDismissListener} to use.
*/
- public void setOnDismissListener(final OnDismissListener listener) {
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnDismissListener is already taken by "
@@ -1254,7 +1267,7 @@
* Sets a listener to be invoked when the dialog is shown.
* @param listener The {@link DialogInterface.OnShowListener} to use.
*/
- public void setOnShowListener(OnShowListener listener) {
+ public void setOnShowListener(@Nullable OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
@@ -1266,13 +1279,13 @@
* Set a message to be sent when the dialog is dismissed.
* @param msg The msg to send when the dialog is dismissed.
*/
- public void setDismissMessage(final Message msg) {
+ public void setDismissMessage(@Nullable Message msg) {
mDismissMessage = msg;
}
/** @hide */
- public boolean takeCancelAndDismissListeners(String msg, final OnCancelListener cancel,
- final OnDismissListener dismiss) {
+ public boolean takeCancelAndDismissListeners(@Nullable String msg,
+ @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
if (mCancelAndDismissTaken != null) {
mCancelAndDismissTaken = null;
} else if (mCancelMessage != null || mDismissMessage != null) {
@@ -1306,15 +1319,15 @@
/**
* Sets the callback that will be called if a key is dispatched to the dialog.
*/
- public void setOnKeyListener(final OnKeyListener onKeyListener) {
+ public void setOnKeyListener(@Nullable OnKeyListener onKeyListener) {
mOnKeyListener = onKeyListener;
}
private static final class ListenersHandler extends Handler {
- private WeakReference<DialogInterface> mDialog;
+ private final WeakReference<DialogInterface> mDialog;
public ListenersHandler(Dialog dialog) {
- mDialog = new WeakReference<DialogInterface>(dialog);
+ mDialog = new WeakReference<>(dialog);
}
@Override
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index bb3c719..c745644 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1033,11 +1033,11 @@
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
- if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null) {
+ if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
- mDeferStart = !isVisibleToUser;
+ mDeferStart = mState < STARTED && !isVisibleToUser;
}
/**
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 3f22385..4c4f128 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -440,6 +440,11 @@
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
+ if (DEBUG) {
+ Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
+ + " with key=" + key);
+ }
+
synchronized (this) {
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
@@ -651,6 +656,16 @@
activityResources.overrideConfig.setToDefaults();
}
+ if (DEBUG) {
+ Throwable here = new Throwable();
+ here.fillInStackTrace();
+ Slog.d(TAG, "updating resources override for activity=" + activityToken
+ + " from oldConfig=" + Configuration.resourceQualifierString(oldConfig)
+ + " to newConfig="
+ + Configuration.resourceQualifierString(activityResources.overrideConfig),
+ here);
+ }
+
final boolean activityHasOverrideConfig =
!activityResources.overrideConfig.equals(Configuration.EMPTY);
@@ -692,9 +707,15 @@
oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
rebasedOverrideConfig, oldKey.mCompatInfo);
+ if (DEBUG) {
+ Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+ + " to newKey=" + newKey);
+ }
+
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
if (resourcesImpl == null) {
resourcesImpl = createResourcesImpl(newKey);
+ mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
}
if (resourcesImpl != resources.getImpl()) {
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 276f774..2c1ee8e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -36,6 +36,7 @@
import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
+
import libcore.io.IoUtils;
import java.io.FileOutputStream;
@@ -77,6 +78,7 @@
private int mOwningUid;
+ @Override
public void connect(IAccessibilityServiceClient client, int flags) {
if (client == null) {
throw new IllegalArgumentException("Client cannot be null!");
@@ -326,11 +328,12 @@
int flags) {
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+ | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
+ | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 7d698b3..01f82e6 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -284,6 +284,8 @@
out.writeInt(mInstance);
out.writeInt(mProperties);
out.writeInt(mPermissions);
+ out.writeInt(mKeySize);
+ out.writeInt(mWriteType);
out.writeTypedList(mDescriptors);
}
@@ -303,6 +305,8 @@
mInstance = in.readInt();
mProperties = in.readInt();
mPermissions = in.readInt();
+ mKeySize = in.readInt();
+ mWriteType = in.readInt();
mDescriptors = new ArrayList<BluetoothGattDescriptor>();
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index e355a1c..35437a1 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -144,7 +144,7 @@
}
/**
- *
+ *
* Get a list of devices that match any of the given connection
* states.
*
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 3a17e23..ed5dfa5 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -883,6 +883,15 @@
return true;
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AuthorityEntry) {
+ final AuthorityEntry other = (AuthorityEntry)obj;
+ return match(other);
+ }
+ return false;
+ }
+
/**
* Determine whether this AuthorityEntry matches the given data Uri.
* <em>Note that this comparison is case-sensitive, unlike formal
@@ -917,7 +926,7 @@
}
return MATCH_CATEGORY_HOST;
}
- };
+ }
/**
* Add a new Intent data "scheme specific part" to match against. The filter must
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 984a960..65e0b92 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -113,6 +113,13 @@
public static final int PROTECTION_FLAG_PREINSTALLED = 0x400;
/**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>setup</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_SETUP = 0x800;
+
+ /**
* Mask for {@link #protectionLevel}: the basic protection type.
*/
public static final int PROTECTION_MASK_BASE = 0xf;
@@ -226,6 +233,9 @@
if ((level&PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
protLevel += "|preinstalled";
}
+ if ((level&PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
+ protLevel += "|setup";
+ }
return protLevel;
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 4add962..d06e08b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2025,11 +2025,20 @@
* produced in response to a capture request submitted
* while in HDR mode.</p>
* <p>Since substantial post-processing is generally needed to
- * produce an HDR image, only YUV and JPEG outputs are
- * supported for LIMITED/FULL device HDR captures, and only
- * JPEG outputs are supported for LEGACY HDR
- * captures. Using a RAW output for HDR capture is not
+ * produce an HDR image, only YUV, PRIVATE, and JPEG
+ * outputs are supported for LIMITED/FULL device HDR
+ * captures, and only JPEG outputs are supported for LEGACY
+ * HDR captures. Using a RAW output for HDR capture is not
* supported.</p>
+ * <p>Some devices may also support always-on HDR, which
+ * applies HDR processing at full frame rate. For these
+ * devices, intents other than STILL_CAPTURE will also
+ * produce an HDR output with no frame rate impact compared
+ * to normal operation, though the quality may be lower
+ * than for STILL_CAPTURE intents.</p>
+ * <p>If SCENE_MODE_HDR is used with unsupported output types
+ * or capture intents, the images captured will be as if
+ * the SCENE_MODE was not enabled at all.</p>
*
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CaptureRequest#CONTROL_SCENE_MODE
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 5748726..4f41e1c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3373,7 +3373,7 @@
/**
* <p>Maximum raw value output by sensor for this frame.</p>
- * <p>Since the android.sensor.blackLevel may change for different
+ * <p>Since the {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} may change for different
* capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}), the white
* level will change accordingly. This key is similar to
* {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}, but specifies the camera device
@@ -3385,6 +3385,7 @@
* >= 0</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
+ * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
* @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
* @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
* @see CaptureRequest#SENSOR_SENSITIVITY
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index 2c2ad1c..b0b94e3 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -70,7 +70,7 @@
* CameraDeviceStateListener callbacks to be called after state transitions.
*/
public interface CameraDeviceStateListener {
- void onError(int errorCode, RequestHolder holder);
+ void onError(int errorCode, Object errorArg, RequestHolder holder);
void onConfiguring();
void onIdle();
void onBusy();
@@ -162,11 +162,12 @@
* @param captureError Report a recoverable error for a single buffer or result using a valid
* error code for {@code ICameraDeviceCallbacks}, or
* {@link #NO_CAPTURE_ERROR}.
+ * @param captureErrorArg An argument for some error captureError codes.
* @return {@code false} if an error has occurred.
*/
public synchronized boolean setCaptureResult(final RequestHolder request,
- final CameraMetadataNative result,
- final int captureError) {
+ final CameraMetadataNative result,
+ final int captureError, final Object captureErrorArg) {
if (mCurrentState != STATE_CAPTURING) {
Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
@@ -179,7 +180,7 @@
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
- mCurrentListener.onError(captureError, request);
+ mCurrentListener.onError(captureError, captureErrorArg, request);
}
});
} else {
@@ -194,6 +195,11 @@
return mCurrentError == NO_CAPTURE_ERROR;
}
+ public synchronized boolean setCaptureResult(final RequestHolder request,
+ final CameraMetadataNative result) {
+ return setCaptureResult(request, result, NO_CAPTURE_ERROR, /*errorArg*/null);
+ }
+
/**
* Set the listener for state transition callbacks.
*
@@ -239,7 +245,7 @@
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
- mCurrentListener.onError(mCurrentError, mCurrentRequest);
+ mCurrentListener.onError(mCurrentError, /*errorArg*/null, mCurrentRequest);
}
});
}
@@ -299,7 +305,7 @@
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
- mCurrentListener.onError(error, mCurrentRequest);
+ mCurrentListener.onError(error, /*errorArg*/null, mCurrentRequest);
}
});
} else {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index d01c275..f99928a 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -480,19 +480,15 @@
throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
}
- ArrayList<Surface> surfaces = null;
+ SparseArray<Surface> surfaces = null;
synchronized(mConfigureLock) {
if (!mConfiguring) {
String err = "Cannot end configure, no configuration change in progress.";
Log.e(TAG, err);
throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
}
- int numSurfaces = mSurfaces.size();
- if (numSurfaces > 0) {
- surfaces = new ArrayList<>();
- for (int i = 0; i < numSurfaces; ++i) {
- surfaces.add(mSurfaces.valueAt(i));
- }
+ if (mSurfaces != null) {
+ surfaces = mSurfaces.clone();
}
mConfiguring = false;
}
diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
index eb48a01..113927c 100644
--- a/core/java/android/hardware/camera2/legacy/CaptureCollector.java
+++ b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
@@ -19,7 +19,7 @@
import android.util.Log;
import android.util.MutableLong;
import android.util.Pair;
-
+import android.view.Surface;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -95,22 +95,28 @@
} else {
// Send buffer dropped errors for each pending buffer if the request has
// started.
- if (mFailedPreview) {
- Log.w(TAG, "Preview buffers dropped for request: " +
- mRequest.getRequestId());
- for (int i = 0; i < mRequest.numPreviewTargets(); i++) {
- CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
- /*result*/null,
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
- }
- }
- if (mFailedJpeg) {
- Log.w(TAG, "Jpeg buffers dropped for request: " +
- mRequest.getRequestId());
- for (int i = 0; i < mRequest.numJpegTargets(); i++) {
- CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
- /*result*/null,
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
+ for (Surface targetSurface : mRequest.getRequest().getTargets() ) {
+ try {
+ if (mRequest.jpegType(targetSurface)) {
+ if (mFailedJpeg) {
+ CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
+ /*result*/null,
+ CameraDeviceImpl.CameraDeviceCallbacks.
+ ERROR_CAMERA_BUFFER,
+ targetSurface);
+ }
+ } else {
+ // preview buffer
+ if (mFailedPreview) {
+ CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
+ /*result*/null,
+ CameraDeviceImpl.CameraDeviceCallbacks.
+ ERROR_CAMERA_BUFFER,
+ targetSurface);
+ }
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.e(TAG, "Unexpected exception when querying Surface: " + e);
}
}
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 661edd7..6c95869 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Size;
+import android.util.SparseArray;
import android.view.Surface;
import java.util.ArrayList;
@@ -64,7 +65,7 @@
private final CameraCharacteristics mStaticCharacteristics;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
- private List<Surface> mConfiguredSurfaces;
+ private SparseArray<Surface> mConfiguredSurfaces;
private boolean mClosed = false;
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
@@ -89,13 +90,29 @@
public static final int NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW = 1;
private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
+ return getExtrasFromRequest(holder,
+ /*errorCode*/CameraDeviceState.NO_CAPTURE_ERROR, /*errorArg*/null);
+ }
+
+ private CaptureResultExtras getExtrasFromRequest(RequestHolder holder,
+ int errorCode, Object errorArg) {
+ int errorStreamId = -1;
+ if (errorCode == CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER) {
+ Surface errorTarget = (Surface) errorArg;
+ int indexOfTarget = mConfiguredSurfaces.indexOfValue(errorTarget);
+ if (indexOfTarget < 0) {
+ Log.e(TAG, "Buffer drop error reported for unknown Surface");
+ } else {
+ errorStreamId = mConfiguredSurfaces.keyAt(indexOfTarget);
+ }
+ }
if (holder == null) {
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
}
return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
/*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
- /*partialResultCount*/1, /*errorStreamId*/-1);
+ /*partialResultCount*/1, errorStreamId);
}
/**
@@ -105,9 +122,9 @@
private final CameraDeviceState.CameraDeviceStateListener mStateListener =
new CameraDeviceState.CameraDeviceStateListener() {
@Override
- public void onError(final int errorCode, final RequestHolder holder) {
+ public void onError(final int errorCode, final Object errorArg, final RequestHolder holder) {
if (DEBUG) {
- Log.d(TAG, "onError called, errorCode = " + errorCode);
+ Log.d(TAG, "onError called, errorCode = " + errorCode + ", errorArg = " + errorArg);
}
switch (errorCode) {
/*
@@ -125,7 +142,7 @@
}
}
- final CaptureResultExtras extras = getExtrasFromRequest(holder);
+ final CaptureResultExtras extras = getExtrasFromRequest(holder, errorCode, errorArg);
mResultHandler.post(new Runnable() {
@Override
public void run() {
@@ -281,14 +298,17 @@
*
* <p>Every surface in {@code outputs} must be non-{@code null}.</p>
*
- * @param outputs a list of surfaces to set.
+ * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
+ * list; it must not be modified by the caller once it's passed in.
* @return an error code for this binder operation, or {@link NO_ERROR}
* on success.
*/
- public int configureOutputs(List<Surface> outputs) {
+ public int configureOutputs(SparseArray<Surface> outputs) {
List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>();
if (outputs != null) {
- for (Surface output : outputs) {
+ int count = outputs.size();
+ for (int i = 0; i < count; i++) {
+ Surface output = outputs.valueAt(i);
if (output == null) {
Log.e(TAG, "configureOutputs - null outputs are not allowed");
return BAD_VALUE;
@@ -353,7 +373,7 @@
}
if (success) {
- mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
+ mConfiguredSurfaces = outputs;
} else {
return LegacyExceptionUtils.INVALID_OPERATION;
}
@@ -659,6 +679,23 @@
return nativeGetSurfaceId(surface);
}
+ static List<Long> getSurfaceIds(SparseArray<Surface> surfaces) {
+ if (surfaces == null) {
+ throw new NullPointerException("Null argument surfaces");
+ }
+ List<Long> surfaceIds = new ArrayList<>();
+ int count = surfaces.size();
+ for (int i = 0; i < count; i++) {
+ long id = getSurfaceId(surfaces.valueAt(i));
+ if (id == 0) {
+ throw new IllegalStateException(
+ "Configured surface had null native GraphicBufferProducer pointer!");
+ }
+ surfaceIds.add(id);
+ }
+ return surfaceIds;
+ }
+
static List<Long> getSurfaceIds(Collection<Surface> surfaces) {
if (surfaces == null) {
throw new NullPointerException("Null argument surfaces");
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 9b628fb..476c3de 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -41,6 +41,8 @@
private final int mNumPreviewTargets;
private volatile boolean mFailed = false;
+ private final Collection<Long> mJpegSurfaceIds;
+
/**
* A builder class for {@link RequestHolder} objects.
*
@@ -150,13 +152,13 @@
*/
public RequestHolder build(long frameNumber) {
return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
- mNumJpegTargets, mNumPreviewTargets);
+ mNumJpegTargets, mNumPreviewTargets, mJpegSurfaceIds);
}
}
private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
boolean repeating, long frameNumber, int numJpegTargets,
- int numPreviewTargets) {
+ int numPreviewTargets, Collection<Long> jpegSurfaceIds) {
mRepeating = repeating;
mRequest = request;
mRequestId = requestId;
@@ -164,6 +166,7 @@
mFrameNumber = frameNumber;
mNumJpegTargets = numJpegTargets;
mNumPreviewTargets = numPreviewTargets;
+ mJpegSurfaceIds = jpegSurfaceIds;
}
/**
@@ -238,6 +241,17 @@
}
/**
+ * Returns true if the given surface requires jpeg buffers.
+ *
+ * @param s a {@link android.view.Surface} to check.
+ * @return true if the surface requires a jpeg buffer.
+ */
+ public boolean jpegType(Surface s)
+ throws LegacyExceptionUtils.BufferQueueAbandonedException {
+ return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
+ }
+
+ /**
* Mark this request as failed.
*/
public void failRequest() {
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index e8ce3ec..a3fdd56 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -908,8 +908,7 @@
mFaceDetectMapper.mapResultFaces(result, mLastRequest);
if (!holder.requestFailed()) {
- mDeviceState.setCaptureResult(holder, result,
- CameraDeviceState.NO_CAPTURE_ERROR);
+ mDeviceState.setCaptureResult(holder, result);
}
}
if (DEBUG) {
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 644e29f..ae44f1d 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -37,6 +37,7 @@
private float mStoppedPowerDrawMw;
private float mSleepPowerDrawMw;
private float mPeakPowerDrawMw;
+ private int mMaxPacketLengthBytes;
private int[] mSupportedSensors;
@@ -46,6 +47,27 @@
}
/**
+ * returns the maximum number of bytes that can be sent per message to the hub
+ *
+ * @return int - maximum bytes that can be transmitted in a
+ * single packet
+ */
+ public int getMaxPacketLengthBytes() {
+ return mMaxPacketLengthBytes;
+ }
+
+ /**
+ * set the context hub unique identifer
+ *
+ * @param bytes - Maximum number of bytes per message
+ *
+ * @hide
+ */
+ public void setMaxPacketLenBytes(int bytes) {
+ mMaxPacketLengthBytes = bytes;
+ }
+
+ /**
* get the context hub unique identifer
*
* @return int - unique system wide identifier
@@ -374,4 +396,4 @@
return new ContextHubInfo[size];
}
};
-}
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 4ddf767..89edaa9 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -43,23 +43,6 @@
private Handler mCallbackHandler;
/**
- * A special context hub identifier meaning any possible hub on the system.
- */
- public static final int ANY_HUB = -1;
- /**
- * A constant denoting a message to load a a Nano App
- */
- public static final int MSG_LOAD_NANO_APP = 1;
- /**
- * A constant denoting a message to unload a a Nano App
- */
- public static final int MSG_UNLOAD_NANO_APP = 2;
- /**
- * A constant denoting a message to send a message
- */
- public static final int MSG_DATA_SEND = 3;
-
- /**
* An interface to receive asynchronous communication from the context hub.
*/
public abstract static class Callback {
@@ -69,7 +52,7 @@
* Callback function called on message receipt from context hub.
*
* @param hubHandle Handle (system-wide unique identifier) of the hub of the message.
- * @param nanoAppHandle Handle (unique identifier) for the app that sent the message.
+ * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message.
* @param message The context hub message.
*
* @see ContextHubMessage
@@ -89,7 +72,7 @@
try {
retVal = getBinder().getContextHubHandles();
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch context hub handles : " + e);
+ Log.w(TAG, "Could not fetch context hub handles : " + e);
}
return retVal;
}
@@ -107,7 +90,7 @@
try {
retVal = getBinder().getContextHubInfo(hubHandle);
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch context hub info :" + e);
+ Log.w(TAG, "Could not fetch context hub info :" + e);
}
return retVal;
@@ -126,6 +109,7 @@
*/
public int loadNanoApp(int hubHandle, NanoApp app) {
int retVal = -1;
+
if (app == null) {
return retVal;
}
@@ -133,7 +117,7 @@
try {
retVal = getBinder().loadNanoApp(hubHandle, app);
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch load nanoApp :" + e);
+ Log.w(TAG, "Could not load nanoApp :" + e);
}
return retVal;
@@ -152,7 +136,7 @@
try {
retVal = getBinder().unloadNanoApp(nanoAppHandle);
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch unload nanoApp :" + e);
+ Log.w(TAG, "Could not fetch unload nanoApp :" + e);
}
return retVal;
@@ -172,7 +156,7 @@
try {
retVal = getBinder().getNanoAppInstanceInfo(nanoAppHandle);
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch nanoApp info :" + e);
+ Log.w(TAG, "Could not fetch nanoApp info :" + e);
}
return retVal;
@@ -193,7 +177,7 @@
try {
retVal = getBinder().findNanoAppOnHub(hubHandle, filter);
} catch (RemoteException e) {
- Log.e(TAG, "Could not query nanoApp instance :" + e);
+ Log.w(TAG, "Could not query nanoApp instance :" + e);
}
return retVal;
}
@@ -212,10 +196,14 @@
public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) {
int retVal = -1;
+ if (message == null || message.getData() == null) {
+ Log.w(TAG, "null ptr");
+ return retVal;
+ }
try {
retVal = getBinder().sendMessage(hubHandle, nanoAppHandle, message);
} catch (RemoteException e) {
- Log.e(TAG, "Could not fetch send message :" + e.toString());
+ Log.w(TAG, "Could not send message :" + e.toString());
}
return retVal;
@@ -247,7 +235,7 @@
public int registerCallback(Callback callback, Handler handler) {
synchronized(this) {
if (mCallback != null) {
- Log.e(TAG, "Max number of callbacks reached!");
+ Log.w(TAG, "Max number of callbacks reached!");
return -1;
}
mCallback = callback;
@@ -268,7 +256,7 @@
public int unregisterCallback(Callback callback) {
synchronized(this) {
if (callback != mCallback) {
- Log.e(TAG, "Cannot recognize callback!");
+ Log.w(TAG, "Cannot recognize callback!");
return -1;
}
@@ -311,11 +299,11 @@
try {
getBinder().registerCallback(mClientCallback);
} catch (RemoteException e) {
- Log.e(TAG, "Could not register callback:" + e);
+ Log.w(TAG, "Could not register callback:" + e);
}
} else {
- Log.d(TAG, "failed to getService");
+ Log.w(TAG, "failed to getService");
}
}
diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java
index 954e97d..bca2ae6 100644
--- a/core/java/android/hardware/location/ContextHubMessage.java
+++ b/core/java/android/hardware/location/ContextHubMessage.java
@@ -16,10 +16,10 @@
package android.hardware.location;
-
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.util.Arrays;
@@ -32,6 +32,9 @@
private int mVersion;
private byte[]mData;
+ private static final String TAG = "ContextHubMessage";
+
+
/**
* Get the message type
*
@@ -106,9 +109,11 @@
private ContextHubMessage(Parcel in) {
mType = in.readInt();
mVersion = in.readInt();
- byte[] byteBuffer = new byte[in.readInt()];
- in.readByteArray(byteBuffer);
+ int bufferLength = in.readInt();
+ mData = new byte[bufferLength];
+ in.readByteArray(mData);
}
+
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mType);
out.writeInt(mVersion);
diff --git a/core/java/android/hardware/location/ContextHubService.java b/core/java/android/hardware/location/ContextHubService.java
index 274babe..b65e24e 100644
--- a/core/java/android/hardware/location/ContextHubService.java
+++ b/core/java/android/hardware/location/ContextHubService.java
@@ -29,12 +29,30 @@
*/
public class ContextHubService extends IContextHubService.Stub {
+ public static final String CONTEXTHUB_SERVICE = "contexthub_service";
+
private static final String TAG = "ContextHubService";
private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
- public static final String CONTEXTHUB_SERVICE = "contexthub_service";
+
+ public static final int ANY_HUB = -1;
+ public static final int MSG_LOAD_NANO_APP = 5;
+ public static final int MSG_UNLOAD_NANO_APP = 2;
+
+ private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
+ private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
+ private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
+ private static final int PRE_LOADED_APP_MEM_REQ = 0;
+
+ private static final int MSG_HEADER_SIZE = 4;
+ private static final int MSG_FIELD_TYPE = 0;
+ private static final int MSG_FIELD_VERSION = 1;
+ private static final int MSG_FIELD_HUB_HANDLE = 2;
+ private static final int MSG_FIELD_APP_INSTANCE = 3;
+
+ private static final int OS_APP_INSTANCE = -1;
private final Context mContext;
@@ -42,44 +60,27 @@
private ContextHubInfo[] mContextHubInfo;
private IContextHubCallback mCallback;
+ private native int nativeSendMessage(int[] header, byte[] data);
+ private native ContextHubInfo[] nativeInitialize();
+
+
public ContextHubService(Context context) {
mContext = context;
mContextHubInfo = nativeInitialize();
+ mNanoAppHash = new HashMap<Integer, NanoAppInstanceInfo>();
for (int i = 0; i < mContextHubInfo.length; i++) {
- Log.v(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
+ Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
+ ", name: " + mContextHubInfo[i].getName());
}
}
- private native int nativeSendMessage(int[] header, byte[] data);
- private native ContextHubInfo[] nativeInitialize();
-
@Override
- public int registerCallback(IContextHubCallback callback) throws RemoteException{
+ public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
- mCallback = callback;
- return 0;
- }
-
-
- private int onMessageReceipt(int[] header, byte[] data) {
- if (mCallback != null) {
- // TODO : Defend against unexpected header sizes
- // Add abstraction for magic numbers
- // onMessageRecipt should pass the right arguments
- ContextHubMessage msg = new ContextHubMessage(header[0], header[1], data);
-
- try {
- mCallback.onMessageReceipt(0, 0, msg);
- } catch (Exception e) {
- Log.e(TAG, "Exception " + e + " when calling remote callback");
- return -1;
- }
- } else {
- Log.d(TAG, "Message Callback is NULL");
+ synchronized(this) {
+ mCallback = callback;
}
-
return 0;
}
@@ -118,14 +119,17 @@
}
// Call Native interface here
- int[] msgHeader = new int[8];
- msgHeader[0] = contextHubHandle;
- msgHeader[1] = app.getAppId();
- msgHeader[2] = app.getAppVersion();
- msgHeader[3] = ContextHubManager.MSG_LOAD_NANO_APP;
- msgHeader[4] = 0; // Loading hints
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[MSG_FIELD_HUB_HANDLE] = contextHubHandle;
+ msgHeader[MSG_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
+ msgHeader[MSG_FIELD_VERSION] = 0;
+ msgHeader[MSG_FIELD_TYPE] = MSG_LOAD_NANO_APP;
- return nativeSendMessage(msgHeader, app.getAppBinary());
+ if (nativeSendMessage(msgHeader, app.getAppBinary()) != 0) {
+ return -1;
+ }
+ // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app
+ return 0;
}
@Override
@@ -137,12 +141,18 @@
}
// Call Native interface here
- int[] msgHeader = new int[8];
- msgHeader[0] = info.getContexthubId();
- msgHeader[1] = ContextHubManager.MSG_UNLOAD_NANO_APP;
- msgHeader[2] = info.getHandle();
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[MSG_FIELD_HUB_HANDLE] = ANY_HUB;
+ msgHeader[MSG_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
+ msgHeader[MSG_FIELD_VERSION] = 0;
+ msgHeader[MSG_FIELD_TYPE] = MSG_UNLOAD_NANO_APP;
- return nativeSendMessage(msgHeader, null);
+ if(nativeSendMessage(msgHeader, null) != 0) {
+ return -1;
+ }
+
+ // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app
+ return 0;
}
@Override
@@ -166,7 +176,7 @@
for(Integer nanoAppInstance : mNanoAppHash.keySet()) {
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
- if(filter.testMatch(info)){
+ if (filter.testMatch(info)){
foundInstances.add(nanoAppInstance);
}
}
@@ -183,12 +193,12 @@
public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
throws RemoteException {
checkPermissions();
- int[] msgHeader = new int[8];
- msgHeader[0] = ContextHubManager.MSG_DATA_SEND;
- msgHeader[1] = hubHandle;
- msgHeader[2] = nanoAppHandle;
- msgHeader[3] = msg.getMsgType();
- msgHeader[4] = msg.getVersion();
+
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[MSG_FIELD_HUB_HANDLE] = hubHandle;
+ msgHeader[MSG_FIELD_APP_INSTANCE] = nanoAppHandle;
+ msgHeader[MSG_FIELD_VERSION] = msg.getVersion();
+ msgHeader[MSG_FIELD_TYPE] = msg.getMsgType();
return nativeSendMessage(msgHeader, msg.getData());
}
@@ -196,5 +206,52 @@
private void checkPermissions() {
mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
}
-}
+ private int onMessageReceipt(int[] header, byte[] data) {
+ if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
+ return -1;
+ }
+
+ synchronized(this) {
+ if (mCallback != null) {
+ ContextHubMessage msg = new ContextHubMessage(header[MSG_FIELD_TYPE],
+ header[MSG_FIELD_VERSION],
+ data);
+
+ try {
+ mCallback.onMessageReceipt(header[MSG_FIELD_HUB_HANDLE],
+ header[MSG_FIELD_APP_INSTANCE],
+ msg);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception " + e + " when calling remote callback");
+ return -1;
+ }
+ } else {
+ Log.d(TAG, "Message Callback is NULL");
+ }
+ }
+
+ return 0;
+ }
+
+ private int addAppInstance(int hubHandle, int appInstanceHandle, long appId, int appVersion) {
+ // App Id encodes vendor & version
+ NanoAppInstanceInfo appInfo = new NanoAppInstanceInfo();
+
+ appInfo.setAppId(appId);
+ appInfo.setAppVersion(appVersion);
+ appInfo.setName(PRE_LOADED_APP_NAME);
+ appInfo.setContexthubId(hubHandle);
+ appInfo.setHandle(appInstanceHandle);
+ appInfo.setPublisher(PRE_LOADED_APP_PUBLISHER);
+ appInfo.setNeededExecMemBytes(PRE_LOADED_APP_MEM_REQ);
+ appInfo.setNeededReadMemBytes(PRE_LOADED_APP_MEM_REQ);
+ appInfo.setNeededWriteMemBytes(PRE_LOADED_APP_MEM_REQ);
+
+ mNanoAppHash.put(appInstanceHandle, appInfo);
+ Log.d(TAG, "Added app instance " + appInstanceHandle + " with id " + appId
+ + " version " + appVersion);
+
+ return 0;
+ }
+}
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 369f9e4..8db70e9 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -20,6 +20,7 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
/**
* @hide
@@ -27,6 +28,8 @@
@SystemApi
public class NanoAppFilter {
+ private static final String TAG = "NanoAppFilter";
+
// The appId, can be set to APP_ID_ANY
private long mAppId;
@@ -54,6 +57,10 @@
* If this flag is set, only versions strictly less than the version specified shall match.
*/
public static final int FLAGS_VERSION_LESS_THAN = 4;
+ /**
+ * If this flag is set, only versions strictly equal to the
+ * version specified shall match.
+ */
public static final int FLAGS_VERSION_STRICTLY_EQUAL = 8;
/**
@@ -117,14 +124,9 @@
* @return true if this is a match, false otherwise
*/
public boolean testMatch(NanoAppInstanceInfo info) {
- if ((mContextHubId == HUB_ANY || info.getContexthubId() == mContextHubId) &&
+ return (mContextHubId == HUB_ANY || info.getContexthubId() == mContextHubId) &&
(mAppId == APP_ANY || info.getAppId() == mAppId) &&
- // (mAppIdVendorMask == VENDOR_ANY) TODO : Expose Vendor mask cleanly
- (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()))) {
- return true;
- } else {
- return false;
- }
+ (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()));
}
public static final Parcelable.Creator<NanoAppFilter> CREATOR
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index ac62919..977f645 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -29,7 +29,7 @@
private String mPublisher;
private String mName;
- private int mAppId;
+ private long mAppId;
private int mAppVersion;
private int mNeededReadMemBytes;
@@ -59,6 +59,8 @@
* set the publisher name for the app
*
* @param publisher - name of the publisher
+ *
+ * @hide
*/
public void setPublisher(String publisher) {
mPublisher = publisher;
@@ -77,6 +79,8 @@
* set the name of the app
*
* @param name - name of the app
+ *
+ * @hide
*/
public void setName(String name) {
mName = name;
@@ -87,7 +91,7 @@
*
* @return int - application identifier
*/
- public int getAppId() {
+ public long getAppId() {
return mAppId;
}
@@ -95,8 +99,10 @@
* Set the application identifier
*
* @param appId - application identifier
+ *
+ * @hide
*/
- public void setAppId(int appId) {
+ public void setAppId(long appId) {
mAppId = appId;
}
@@ -113,6 +119,8 @@
* Set the application version
*
* @param appVersion - version of the app
+ *
+ * @hide
*/
public void setAppVersion(int appVersion) {
mAppVersion = appVersion;
@@ -131,6 +139,8 @@
* Set the read memory needed by the app
*
* @param neededReadMemBytes - readable Memory needed in bytes
+ *
+ * @hide
*/
public void setNeededReadMemBytes(int neededReadMemBytes) {
mNeededReadMemBytes = neededReadMemBytes;
@@ -150,6 +160,8 @@
*
* @param neededWriteMemBytes - writable memory needed by the
* app
+ *
+ * @hide
*/
public void setNeededWriteMemBytes(int neededWriteMemBytes) {
mNeededWriteMemBytes = neededWriteMemBytes;
@@ -169,6 +181,8 @@
*
* @param neededExecMemBytes - executable memory needed by the
* app
+ *
+ * @hide
*/
public void setNeededExecMemBytes(int neededExecMemBytes) {
mNeededExecMemBytes = neededExecMemBytes;
@@ -187,6 +201,8 @@
* set the sensors needed by this app
*
* @param neededSensors - all the sensors needed by this app
+ *
+ * @hide
*/
public void setNeededSensors(int[] neededSensors) {
mNeededSensors = neededSensors;
@@ -206,6 +222,8 @@
*
* @param outputEvents - the events that may be generated by
* this app
+ *
+ * @hide
*/
public void setOutputEvents(int[] outputEvents) {
mOutputEvents = outputEvents;
@@ -224,6 +242,8 @@
* set the context hub identifier
*
* @param contexthubId - system wide unique identifier
+ *
+ * @hide
*/
public void setContexthubId(int contexthubId) {
mContexthubId = contexthubId;
@@ -242,6 +262,8 @@
* set the handle for an app instance
*
* @param handle - handle to this instance
+ *
+ * @hide
*/
public void setHandle(int handle) {
mHandle = handle;
@@ -252,7 +274,7 @@
mPublisher = in.readString();
mName = in.readString();
- mAppId = in.readInt();
+ mAppId = in.readLong();
mAppVersion = in.readInt();
mNeededReadMemBytes = in.readInt();
mNeededWriteMemBytes = in.readInt();
@@ -274,7 +296,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeString(mPublisher);
out.writeString(mName);
- out.writeInt(mAppId);
+ out.writeLong(mAppId);
out.writeInt(mAppVersion);
out.writeInt(mContexthubId);
out.writeInt(mNeededReadMemBytes);
@@ -286,7 +308,6 @@
out.writeInt(mOutputEvents.length);
out.writeIntArray(mOutputEvents);
-
}
public static final Parcelable.Creator<NanoAppInstanceInfo> CREATOR
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 6b79a8a..9d53a00 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -295,9 +295,7 @@
boolean mLastShowInputRequested;
int mCandidatesVisibility;
CompletionInfo[] mCurCompletions;
-
- boolean mShowInputForced;
-
+
boolean mFullscreenApplied;
boolean mIsFullscreen;
View mExtractView;
@@ -422,7 +420,6 @@
boolean wasVis = isInputViewShown();
mShowInputFlags = 0;
mShowInputRequested = false;
- mShowInputForced = false;
doHideWindow();
clearInsetOfPreviousIme();
if (resultReceiver != null) {
@@ -439,8 +436,7 @@
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
boolean wasVis = isInputViewShown();
- mShowInputFlags = 0;
- if (onShowInputRequested(flags, false)) {
+ if (dispatchOnShowInputRequested(flags, false)) {
try {
showWindow(true);
} catch (BadTokenException e) {
@@ -817,8 +813,8 @@
mInitialized = false;
mWindowCreated = false;
mShowInputRequested = false;
- mShowInputForced = false;
-
+ mShowInputFlags = 0;
+
mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
mRootView = mInflater.inflate(
com.android.internal.R.layout.input_method, null);
@@ -888,7 +884,7 @@
*/
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
-
+
boolean visible = mWindowVisible;
int showFlags = mShowInputFlags;
boolean showingInput = mShowInputRequested;
@@ -903,7 +899,7 @@
if (visible) {
if (showingInput) {
// If we were last showing the soft keyboard, try to do so again.
- if (onShowInputRequested(showFlags, true)) {
+ if (dispatchOnShowInputRequested(showFlags, true)) {
showWindow(true);
if (completions != null) {
mCurCompletions = completions;
@@ -1540,20 +1536,41 @@
return false;
}
}
- if ((flags&InputMethod.SHOW_FORCED) != 0) {
- mShowInputForced = true;
- }
return true;
}
-
+
+ /**
+ * A utility method to call {{@link #onShowInputRequested(int, boolean)}} and update internal
+ * states depending on its result. Since {@link #onShowInputRequested(int, boolean)} is
+ * exposed to IME authors as an overridable public method without {@code @CallSuper}, we have
+ * to have this method to ensure that those internal states are always updated no matter how
+ * {@link #onShowInputRequested(int, boolean)} is overridden by the IME author.
+ * @param flags Provides additional information about the show request,
+ * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+ * @param configChange This is true if we are re-showing due to a
+ * configuration change.
+ * @return Returns true to indicate that the window should be shown.
+ * @see #onShowInputRequested(int, boolean)
+ */
+ private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
+ final boolean result = onShowInputRequested(flags, configChange);
+ if (result) {
+ mShowInputFlags = flags;
+ } else {
+ mShowInputFlags = 0;
+ }
+ return result;
+ }
+
public void showWindow(boolean showInput) {
if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+ " mShowInputRequested=" + mShowInputRequested
+ " mWindowAdded=" + mWindowAdded
+ " mWindowCreated=" + mWindowCreated
+ " mWindowVisible=" + mWindowVisible
- + " mInputStarted=" + mInputStarted);
-
+ + " mInputStarted=" + mInputStarted
+ + " mShowInputFlags=" + mShowInputFlags);
+
if (mInShowWindow) {
Log.w(TAG, "Re-entrance in to showWindow");
return;
@@ -2573,7 +2590,6 @@
p.println(" mShowInputRequested=" + mShowInputRequested
+ " mLastShowInputRequested=" + mLastShowInputRequested
- + " mShowInputForced=" + mShowInputForced
+ " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
p.println(" mCandidatesVisibility=" + mCandidatesVisibility
+ " mFullscreenApplied=" + mFullscreenApplied
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index e9c196d..721c94e 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -20,6 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources.NotFoundException;
@@ -31,6 +32,7 @@
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -49,7 +51,7 @@
@IntDef(flag = true, value = {
COLOR_MODE_MONOCHROME, COLOR_MODE_COLOR
})
- public @interface ColorMode {
+ @interface ColorMode {
}
/** Color mode: Monochrome color scheme, for example one color is used. */
public static final int COLOR_MODE_MONOCHROME = 1 << 0;
@@ -64,7 +66,7 @@
@IntDef(flag = true, value = {
DUPLEX_MODE_NONE, DUPLEX_MODE_LONG_EDGE, DUPLEX_MODE_SHORT_EDGE
})
- public @interface DuplexMode {
+ @interface DuplexMode {
}
/** Duplex mode: No duplexing. */
public static final int DUPLEX_MODE_NONE = 1 << 0;
@@ -76,23 +78,29 @@
private static final int VALID_DUPLEX_MODES =
DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE;
- private MediaSize mMediaSize;
- private Resolution mResolution;
- private Margins mMinMargins;
+ private @Nullable MediaSize mMediaSize;
+ private @Nullable Resolution mResolution;
+ private @Nullable Margins mMinMargins;
- private int mColorMode;
- private int mDuplexMode;
+ private @IntRange(from = 0) int mColorMode;
+ private @IntRange(from = 0) int mDuplexMode;
PrintAttributes() {
/* hide constructor */
}
private PrintAttributes(@NonNull Parcel parcel) {
- mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null;
- mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
- mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
+ mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null;
+ mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
+ mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
mColorMode = parcel.readInt();
+ if (mColorMode != 0) {
+ enforceValidColorMode(mColorMode);
+ }
mDuplexMode = parcel.readInt();
+ if (mDuplexMode != 0) {
+ enforceValidDuplexMode(mDuplexMode);
+ }
}
/**
@@ -179,7 +187,7 @@
* @see #COLOR_MODE_COLOR
* @see #COLOR_MODE_MONOCHROME
*/
- public @ColorMode int getColorMode() {
+ public @IntRange(from = 0) int getColorMode() {
return mColorMode;
}
@@ -214,13 +222,13 @@
/**
* Gets the duplex mode.
*
- * @return The duplex mode.
+ * @return The duplex mode or zero if not set.
*
* @see #DUPLEX_MODE_NONE
* @see #DUPLEX_MODE_LONG_EDGE
* @see #DUPLEX_MODE_SHORT_EDGE
*/
- public @DuplexMode int getDuplexMode() {
+ public @IntRange(from = 0) int getDuplexMode() {
return mDuplexMode;
}
@@ -448,7 +456,7 @@
private static final String LOG_TAG = "MediaSize";
private static final Map<String, MediaSize> sIdToMediaSizeMap =
- new ArrayMap<String, MediaSize>();
+ new ArrayMap<>();
/**
* Unknown media size in portrait mode.
@@ -781,15 +789,15 @@
new MediaSize("JPN_YOU4", "android",
R.string.mediasize_japanese_you4, 4134, 9252);
- private final String mId;
+ private final @NonNull String mId;
/**@hide */
- public final String mLabel;
+ public final @NonNull String mLabel;
/**@hide */
- public final String mPackageName;
+ public final @Nullable String mPackageName;
/**@hide */
- public final int mLabelResId;
- private final int mWidthMils;
- private final int mHeightMils;
+ public final @StringRes int mLabelResId;
+ private final @IntRange(from = 1) int mWidthMils;
+ private final @IntRange(from = 1) int mHeightMils;
/**
* Creates a new instance.
@@ -808,29 +816,7 @@
*/
public MediaSize(String id, String packageName, int labelResId,
int widthMils, int heightMils) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("id cannot be empty.");
- }
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName cannot be empty.");
- }
- if (labelResId <= 0) {
- throw new IllegalArgumentException("labelResId must be greater than zero.");
- }
- if (widthMils <= 0) {
- throw new IllegalArgumentException("widthMils "
- + "cannot be less than or equal to zero.");
- }
- if (heightMils <= 0) {
- throw new IllegalArgumentException("heightMils "
- + "cannot be less than or euqual to zero.");
- }
- mPackageName = packageName;
- mId = id;
- mLabelResId = labelResId;
- mWidthMils = widthMils;
- mHeightMils = heightMils;
- mLabel = null;
+ this(id, null, packageName, widthMils, heightMils, labelResId);
// Build this mapping only for predefined media sizes.
sIdToMediaSizeMap.put(mId, this);
@@ -851,26 +837,7 @@
*/
public MediaSize(@NonNull String id, @NonNull String label,
@IntRange(from = 1) int widthMils, @IntRange(from = 1) int heightMils) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("id cannot be empty.");
- }
- if (TextUtils.isEmpty(label)) {
- throw new IllegalArgumentException("label cannot be empty.");
- }
- if (widthMils <= 0) {
- throw new IllegalArgumentException("widthMils "
- + "cannot be less than or equal to zero.");
- }
- if (heightMils <= 0) {
- throw new IllegalArgumentException("heightMils "
- + "cannot be less than or euqual to zero.");
- }
- mId = id;
- mLabel = label;
- mWidthMils = widthMils;
- mHeightMils = heightMils;
- mLabelResId = 0;
- mPackageName = null;
+ this(id, label, null, widthMils, heightMils, 0);
}
/**
@@ -890,15 +857,37 @@
return definedMediaSizes;
}
- /** @hide */
- public MediaSize(String id, String label, String packageName,
- int widthMils, int heightMils, int labelResId) {
+ /**
+ * Creates a new instance.
+ *
+ * @param id The unique media size id. It is unique amongst other media sizes
+ * supported by the printer.
+ * @param label The <strong>localized</strong> human readable label.
+ * @param packageName The name of the creating package.
+ * @param widthMils The width in mils (thousands of an inch).
+ * @param heightMils The height in mils (thousands of an inch).
+ * @param labelResId The resource if of a human readable label.
+ *
+ * @throws IllegalArgumentException If the id is empty or the label is unset
+ * or the widthMils is less than or equal to zero or the heightMils is less
+ * than or equal to zero.
+ *
+ * @hide
+ */
+ public MediaSize(String id, String label, String packageName, int widthMils, int heightMils,
+ int labelResId) {
mPackageName = packageName;
- mId = id;
+ mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty.");
mLabelResId = labelResId;
- mWidthMils = widthMils;
- mHeightMils = heightMils;
+ mWidthMils = Preconditions.checkArgumentPositive(widthMils, "widthMils cannot be " +
+ "less than or equal to zero.");
+ mHeightMils = Preconditions.checkArgumentPositive(heightMils, "heightMils cannot be " +
+ "less than or equal to zero.");
mLabel = label;
+
+ // The label has to be either a string ot a StringRes
+ Preconditions.checkArgument(!TextUtils.isEmpty(label) !=
+ (!TextUtils.isEmpty(packageName) && labelResId != 0), "label cannot be empty.");
}
/**
@@ -926,10 +915,7 @@
try {
return packageManager.getResourcesForApplication(
mPackageName).getString(mLabelResId);
- } catch (NotFoundException nfe) {
- Log.w(LOG_TAG, "Could not load resouce" + mLabelResId
- + " from package " + mPackageName);
- } catch (NameNotFoundException nnfee) {
+ } catch (NotFoundException | NameNotFoundException e) {
Log.w(LOG_TAG, "Could not load resouce" + mLabelResId
+ " from package " + mPackageName);
}
@@ -1084,10 +1070,10 @@
* the one with 300 DPI resolution.
*/
public static final class Resolution {
- private final String mId;
- private final String mLabel;
- private final int mHorizontalDpi;
- private final int mVerticalDpi;
+ private final @NonNull String mId;
+ private final @NonNull String mLabel;
+ private final @IntRange(from = 1) int mHorizontalDpi;
+ private final @IntRange(from = 1) int mVerticalDpi;
/**
* Creates a new instance.
@@ -1244,8 +1230,7 @@
* @param rightMils The right margin in mils (thousands of an inch).
* @param bottomMils The bottom margin in mils (thousands of an inch).
*/
- public Margins(@IntRange(from = 0) int leftMils, @IntRange(from = 0) int topMils,
- @IntRange(from = 0) int rightMils, @IntRange(from = 0) int bottomMils) {
+ public Margins(int leftMils, int topMils, int rightMils, int bottomMils) {
mTopMils = topMils;
mLeftMils = leftMils;
mRightMils = rightMils;
@@ -1257,7 +1242,7 @@
*
* @return The left margin.
*/
- public @IntRange(from = 0) int getLeftMils() {
+ public int getLeftMils() {
return mLeftMils;
}
@@ -1266,7 +1251,7 @@
*
* @return The top margin.
*/
- public @IntRange(from = 0) int getTopMils() {
+ public int getTopMils() {
return mTopMils;
}
@@ -1275,7 +1260,7 @@
*
* @return The right margin.
*/
- public @IntRange(from = 0) int getRightMils() {
+ public int getRightMils() {
return mRightMils;
}
@@ -1284,7 +1269,7 @@
*
* @return The bottom margin.
*/
- public @IntRange(from = 0) int getBottomMils() {
+ public int getBottomMils() {
return mBottomMils;
}
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index db3b6f4..bec6f29 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -22,6 +22,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -114,8 +115,8 @@
*/
public static final int CONTENT_TYPE_PHOTO = 1;
- private String mName;
- private int mPageCount;
+ private @NonNull String mName;
+ private @IntRange(from = -1) int mPageCount;
private int mContentType;
private long mDataSize;
@@ -144,10 +145,11 @@
* @param parcel Data from which to initialize.
*/
private PrintDocumentInfo(Parcel parcel) {
- mName = parcel.readString();
+ mName = Preconditions.checkStringNotEmpty(parcel.readString());
mPageCount = parcel.readInt();
+ Preconditions.checkArgument(mPageCount == PAGE_COUNT_UNKNOWN || mPageCount > 0);
mContentType = parcel.readInt();
- mDataSize = parcel.readLong();
+ mDataSize = Preconditions.checkArgumentNonnegative(parcel.readLong());
}
/**
@@ -180,7 +182,7 @@
* @see #CONTENT_TYPE_DOCUMENT
* @see #CONTENT_TYPE_PHOTO
*/
- public @ContentType int getContentType() {
+ public int getContentType() {
return mContentType;
}
@@ -262,13 +264,13 @@
builder.append("PrintDocumentInfo{");
builder.append("name=").append(mName);
builder.append(", pageCount=").append(mPageCount);
- builder.append(", contentType=").append(contentTyepToString(mContentType));
+ builder.append(", contentType=").append(contentTypeToString(mContentType));
builder.append(", dataSize=").append(mDataSize);
builder.append("}");
return builder.toString();
}
- private String contentTyepToString(int contentType) {
+ private String contentTypeToString(int contentType) {
switch (contentType) {
case CONTENT_TYPE_DOCUMENT: {
return "CONTENT_TYPE_DOCUMENT";
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index d13879b..01c23f6 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -24,11 +24,13 @@
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
+import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.IntConsumer;
/**
* This class represents the capabilities of a printer. Instances
@@ -55,9 +57,9 @@
private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
- private Margins mMinMargins = DEFAULT_MARGINS;
- private List<MediaSize> mMediaSizes;
- private List<Resolution> mResolutions;
+ private @NonNull Margins mMinMargins = DEFAULT_MARGINS;
+ private @NonNull List<MediaSize> mMediaSizes;
+ private @NonNull List<Resolution> mResolutions;
private int mColorModes;
private int mDuplexModes;
@@ -205,15 +207,37 @@
return builder.build();
}
+ /**
+ * Call enforceSingle for each bit in the mask.
+ *
+ * @param mask The mask
+ * @param enforceSingle The function to call
+ */
+ private static void enforceValidMask(int mask, IntConsumer enforceSingle) {
+ int current = mask;
+ while (current > 0) {
+ final int currentMode = (1 << Integer.numberOfTrailingZeros(current));
+ current &= ~currentMode;
+ enforceSingle.accept(currentMode);
+ }
+ }
+
private PrinterCapabilitiesInfo(Parcel parcel) {
- mMinMargins = readMargins(parcel);
+ mMinMargins = Preconditions.checkNotNull(readMargins(parcel));
readMediaSizes(parcel);
readResolutions(parcel);
mColorModes = parcel.readInt();
+ enforceValidMask(mColorModes,
+ (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode));
+
mDuplexModes = parcel.readInt();
+ enforceValidMask(mDuplexModes,
+ (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode));
readDefaults(parcel);
+ Preconditions.checkArgument(mMediaSizes.size() > mDefaults[PROPERTY_MEDIA_SIZE]);
+ Preconditions.checkArgument(mResolutions.size() > mDefaults[PROPERTY_RESOLUTION]);
}
@Override
@@ -537,12 +561,8 @@
*/
public @NonNull Builder setColorModes(@ColorMode int colorModes,
@ColorMode int defaultColorMode) {
- int currentModes = colorModes;
- while (currentModes > 0) {
- final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
- currentModes &= ~currentMode;
- PrintAttributes.enforceValidColorMode(currentMode);
- }
+ enforceValidMask(colorModes,
+ (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode));
PrintAttributes.enforceValidColorMode(defaultColorMode);
mPrototype.mColorModes = colorModes;
mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
@@ -568,12 +588,8 @@
*/
public @NonNull Builder setDuplexModes(@DuplexMode int duplexModes,
@DuplexMode int defaultDuplexMode) {
- int currentModes = duplexModes;
- while (currentModes > 0) {
- final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
- currentModes &= ~currentMode;
- PrintAttributes.enforceValidDuplexMode(currentMode);
- }
+ enforceValidMask(duplexModes,
+ (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode));
PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
mPrototype.mDuplexModes = duplexModes;
mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 69dd9e5a..f4d63ac 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1569,9 +1569,8 @@
* @return true if the calling app can draw on top of other apps, false otherwise.
*/
public static boolean canDrawOverlays(Context context) {
- int uid = Binder.getCallingUid();
- return Settings.isCallingPackageAllowedToDrawOverlays(context, uid, Settings
- .getPackageNameForUid(context, uid), false);
+ return Settings.isCallingPackageAllowedToDrawOverlays(context, Process.myUid(),
+ context.getOpPackageName(), false);
}
/**
@@ -3885,9 +3884,8 @@
* @return true if the calling app can write to system settings, false otherwise
*/
public static boolean canWrite(Context context) {
- int uid = Binder.getCallingUid();
- return isCallingPackageAllowedToWriteSettings(context, uid, getPackageNameForUid(
- context, uid), false);
+ return isCallingPackageAllowedToWriteSettings(context, Process.myUid(),
+ context.getOpPackageName(), false);
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7af0b05..0557d13 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -35,7 +35,6 @@
import android.view.ActionMode;
import android.view.Display;
import android.view.KeyEvent;
-import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 5bcf102..4ba97d5 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -180,7 +180,12 @@
// caller must call setNewConfiguration() sometime later.
Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
IBinder freezeThisOneIfNeeded);
- void setNewConfiguration(in Configuration config);
+ // Notify window manager of the new configuration. Returns an array of stack ids that's
+ // affected by the update, ActivityManager should resize these stacks.
+ int[] setNewConfiguration(in Configuration config);
+
+ // Retrieves the new bounds after the configuration update evaluated by window manager.
+ Rect getBoundsForNewConfiguration(int stackId);
void startFreezingScreen(int exitAnim, int enterAnim);
void stopFreezingScreen();
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
index b721074..737e4607 100644
--- a/core/java/android/view/WindowInfo.java
+++ b/core/java/android/view/WindowInfo.java
@@ -44,6 +44,8 @@
public boolean focused;
public final Rect boundsInScreen = new Rect();
public List<IBinder> childTokens;
+ public CharSequence title;
+ public int accessibilityIdOfAnchor = View.NO_ID;
private WindowInfo() {
/* do nothing - hide constructor */
@@ -65,6 +67,8 @@
window.parentToken = other.parentToken;
window.focused = other.focused;
window.boundsInScreen.set(other.boundsInScreen);
+ window.title = other.title;
+ window.accessibilityIdOfAnchor = other.accessibilityIdOfAnchor;
if (other.childTokens != null && !other.childTokens.isEmpty()) {
if (window.childTokens == null) {
@@ -95,6 +99,8 @@
parcel.writeStrongBinder(parentToken);
parcel.writeInt(focused ? 1 : 0);
boundsInScreen.writeToParcel(parcel, flags);
+ parcel.writeCharSequence(title);
+ parcel.writeInt(accessibilityIdOfAnchor);
if (childTokens != null && !childTokens.isEmpty()) {
parcel.writeInt(1);
@@ -108,13 +114,15 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("WindowInfo[");
- builder.append("type=").append(type);
+ builder.append("title=").append(title);
+ builder.append(", type=").append(type);
builder.append(", layer=").append(layer);
builder.append(", token=").append(token);
builder.append(", bounds=").append(boundsInScreen);
builder.append(", parent=").append(parentToken);
builder.append(", focused=").append(focused);
builder.append(", children=").append(childTokens);
+ builder.append(", accessibility anchor=").append(accessibilityIdOfAnchor);
builder.append(']');
return builder.toString();
}
@@ -126,6 +134,8 @@
parentToken = parcel.readStrongBinder();
focused = (parcel.readInt() == 1);
boundsInScreen.readFromParcel(parcel);
+ title = parcel.readCharSequence();
+ accessibilityIdOfAnchor = parcel.readInt();
final boolean hasChildren = (parcel.readInt() == 1);
if (hasChildren) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 03dcf99..c372c30 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1693,6 +1693,14 @@
*/
public long userActivityTimeout = -1;
+ /**
+ * For windows with an anchor (e.g. PopupWindow), keeps track of the View that anchors the
+ * window.
+ *
+ * @hide
+ */
+ public int accessibilityIdOfAnchor = -1;
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -1799,6 +1807,7 @@
out.writeInt(surfaceInsets.bottom);
out.writeInt(hasManualSurfaceInsets ? 1 : 0);
out.writeInt(needsMenuKey);
+ out.writeInt(accessibilityIdOfAnchor);
}
public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1849,6 +1858,7 @@
surfaceInsets.bottom = in.readInt();
hasManualSurfaceInsets = in.readInt() != 0;
needsMenuKey = in.readInt();
+ accessibilityIdOfAnchor = in.readInt();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1888,6 +1898,8 @@
/** {@hide} */
public static final int PREFERRED_DISPLAY_MODE_ID = 1 << 23;
/** {@hide} */
+ public static final int ACCESSIBILITY_ANCHOR_CHANGED = 1 << 24;
+ /** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
// internal buffer to backup/restore parameters under compatibility mode.
@@ -2048,6 +2060,11 @@
changes |= NEEDS_MENU_KEY_CHANGED;
}
+ if (accessibilityIdOfAnchor != o.accessibilityIdOfAnchor) {
+ accessibilityIdOfAnchor = o.accessibilityIdOfAnchor;
+ changes |= ACCESSIBILITY_ANCHOR_CHANGED;
+ }
+
return changes;
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 2cde03d..f8a13a3 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -240,10 +240,12 @@
* <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
* was traversed.</li>
* <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #getFromIndex()} - The start of the next/previous text at the specified granularity
- * - inclusive.</li>
- * <li>{@link #getToIndex()} - The end of the next/previous text at the specified granularity
- * - exclusive.</li>
+ * <li>{@link #getFromIndex()} - The start the text that was skipped over in this movement.
+ * This is the starting point when moving forward through the text, but not when moving
+ * back.</li>
+ * <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement.
+ * This is the ending point when moving forward through the text, but not when moving
+ * back.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #getContentDescription()} - The content description of the source.</li>
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index ad78b68..d0d4507 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -89,6 +89,8 @@
private int mParentId = UNDEFINED;
private final Rect mBoundsInScreen = new Rect();
private LongArray mChildIds;
+ private CharSequence mTitle;
+ private int mAnchorId = UNDEFINED;
private int mConnectionId = UNDEFINED;
@@ -97,6 +99,26 @@
}
/**
+ * Gets the title of the window.
+ *
+ * @return The title.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the title of the window.
+ *
+ * @param title The title.
+ *
+ * @hide
+ */
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ }
+
+ /**
* Gets the type of the window.
*
* @return The type.
@@ -159,9 +181,35 @@
}
/**
- * Gets the parent window if such.
+ * Sets the anchor node's ID.
*
- * @return The parent window.
+ * @param anchorId The anchor's accessibility id in its window.
+ *
+ * @hide
+ */
+ public void setAnchorId(int anchorId) {
+ mAnchorId = anchorId;
+ }
+
+ /**
+ * Gets the node that anchors this window to another.
+ *
+ * @return The anchor node, or {@code null} if none exists.
+ */
+ public AccessibilityNodeInfo getAnchor() {
+ if ((mConnectionId == UNDEFINED) || (mAnchorId == UNDEFINED) || (mParentId == UNDEFINED)) {
+ return null;
+ }
+
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mParentId, mAnchorId, true, 0);
+ }
+
+ /**
+ * Gets the parent window.
+ *
+ * @return The parent window, or {@code null} if none exists.
*/
public AccessibilityWindowInfo getParent() {
if (mConnectionId == UNDEFINED || mParentId == UNDEFINED) {
@@ -370,6 +418,8 @@
infoClone.mId = info.mId;
infoClone.mParentId = info.mParentId;
infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
+ infoClone.mTitle = info.mTitle;
+ infoClone.mAnchorId = info.mAnchorId;
if (info.mChildIds != null && info.mChildIds.size() > 0) {
if (infoClone.mChildIds == null) {
@@ -410,6 +460,8 @@
parcel.writeInt(mId);
parcel.writeInt(mParentId);
mBoundsInScreen.writeToParcel(parcel, flags);
+ parcel.writeCharSequence(mTitle);
+ parcel.writeInt(mAnchorId);
final LongArray childIds = mChildIds;
if (childIds == null) {
@@ -432,6 +484,8 @@
mId = parcel.readInt();
mParentId = parcel.readInt();
mBoundsInScreen.readFromParcel(parcel);
+ mTitle = parcel.readCharSequence();
+ mAnchorId = parcel.readInt();
final int childCount = parcel.readInt();
if (childCount > 0) {
@@ -471,6 +525,7 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("AccessibilityWindowInfo[");
+ builder.append("title=").append(mTitle);
builder.append("id=").append(mId);
builder.append(", type=").append(typeToString(mType));
builder.append(", layer=").append(mLayer);
@@ -494,6 +549,7 @@
builder.append(']');
} else {
builder.append(", hasParent=").append(mParentId != UNDEFINED);
+ builder.append(", isAnchored=").append(mAnchorId != UNDEFINED);
builder.append(", hasChildren=").append(mChildIds != null
&& mChildIds.size() > 0);
}
@@ -515,6 +571,8 @@
mChildIds.clear();
}
mConnectionId = UNDEFINED;
+ mAnchorId = UNDEFINED;
+ mTitle = null;
}
/**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index cc496dc..0ac5731 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -21,7 +21,6 @@
import android.app.AppGlobals;
import android.app.Application;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -561,18 +560,6 @@
return result;
}
- /**
- * Returns whether the entire package from an ACTION_PACKAGE_CHANGED intent was changed (rather
- * than just one of its components).
- * @hide
- */
- public static boolean entirePackageChanged(Intent intent) {
- String[] componentList =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
- return Arrays.asList(componentList).contains(
- intent.getDataString().substring("package:".length()));
- }
-
private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
/** @hide */
diff --git a/core/java/android/widget/AccessibilityIterators.java b/core/java/android/widget/AccessibilityIterators.java
index a3d58a4..442ffa1 100644
--- a/core/java/android/widget/AccessibilityIterators.java
+++ b/core/java/android/widget/AccessibilityIterators.java
@@ -134,8 +134,8 @@
@Override
public int[] following(int offset) {
- final int textLegth = mText.length();
- if (textLegth <= 0) {
+ final int textLength = mText.length();
+ if (textLength <= 0) {
return null;
}
if (offset >= mText.length()) {
@@ -163,8 +163,8 @@
@Override
public int[] preceding(int offset) {
- final int textLegth = mText.length();
- if (textLegth <= 0) {
+ final int textLength = mText.length();
+ if (textLength <= 0) {
return null;
}
if (offset <= 0) {
@@ -181,8 +181,13 @@
final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
- mView.getTotalPaddingBottom();
final int previousPageEndY = currentLineTop - pageHeight;
- final int currentPageStartLine = (previousPageEndY > 0) ?
- mLayout.getLineForVertical(previousPageEndY) + 1 : 0;
+ int currentPageStartLine = (previousPageEndY > 0) ?
+ mLayout.getLineForVertical(previousPageEndY) : 0;
+ // If we're at the end of text, we're at the end of the current line rather than the
+ // start of the next line, so we should move up one fewer lines than we would otherwise.
+ if (end == mText.length() && (currentPageStartLine < currentLine)) {
+ currentPageStartLine += 1;
+ }
final int start = getLineEdgeIndex(currentPageStartLine, DIRECTION_START);
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 027f6d6..9d228cf 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -20,6 +20,7 @@
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
@@ -61,17 +62,13 @@
private final LayoutInflater mInflater;
- /**
- * Contains the list of objects that represent the data of this ArrayAdapter.
- * The content of this list is referred to as "the array" in the documentation.
- */
- private List<T> mObjects;
+ private final Context mContext;
/**
* The resource indicating what views to inflate to display the content of this
* array adapter.
*/
- private int mResource;
+ private final int mResource;
/**
* The resource indicating what views to inflate to display the content of this
@@ -80,7 +77,13 @@
private int mDropDownResource;
/**
- * If the inflated resource is not a TextView, {@link #mFieldId} is used to find
+ * Contains the list of objects that represent the data of this ArrayAdapter.
+ * The content of this list is referred to as "the array" in the documentation.
+ */
+ private List<T> mObjects;
+
+ /**
+ * If the inflated resource is not a TextView, {@code mFieldId} is used to find
* a TextView inside the inflated views hierarchy. This field must contain the
* identifier that matches the one defined in the resource file.
*/
@@ -92,8 +95,6 @@
*/
private boolean mNotifyOnChange = true;
- private Context mContext;
-
// A copy of the original mObjects array, initialized from and then used instead as soon as
// the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
private ArrayList<T> mOriginalValues;
@@ -109,8 +110,8 @@
* @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
*/
- public ArrayAdapter(Context context, @LayoutRes int resource) {
- this(context, resource, 0, new ArrayList<T>());
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) {
+ this(context, resource, 0, new ArrayList<>());
}
/**
@@ -121,8 +122,9 @@
* instantiating views.
* @param textViewResourceId The id of the TextView within the layout resource to be populated
*/
- public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId) {
- this(context, resource, textViewResourceId, new ArrayList<T>());
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
+ @IdRes int textViewResourceId) {
+ this(context, resource, textViewResourceId, new ArrayList<>());
}
/**
@@ -133,7 +135,7 @@
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull T[] objects) {
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
this(context, resource, 0, Arrays.asList(objects));
}
@@ -146,8 +148,8 @@
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId,
- @NonNull T[] objects) {
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
+ @IdRes int textViewResourceId, @NonNull T[] objects) {
this(context, resource, textViewResourceId, Arrays.asList(objects));
}
@@ -159,7 +161,8 @@
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull List<T> objects) {
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
+ @NonNull List<T> objects) {
this(context, resource, 0, objects);
}
@@ -172,8 +175,8 @@
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId,
- @NonNull List<T> objects) {
+ public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
+ @IdRes int textViewResourceId, @NonNull List<T> objects) {
mContext = context;
mInflater = LayoutInflater.from(context);
mResource = mDropDownResource = resource;
@@ -186,7 +189,7 @@
*
* @param object The object to add at the end of the array.
*/
- public void add(T object) {
+ public void add(@Nullable T object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.add(object);
@@ -201,8 +204,17 @@
* Adds the specified Collection at the end of the array.
*
* @param collection The Collection to add at the end of the array.
+ * @throws UnsupportedOperationException if the <tt>addAll</tt> operation
+ * is not supported by this list
+ * @throws ClassCastException if the class of an element of the specified
+ * collection prevents it from being added to this list
+ * @throws NullPointerException if the specified collection contains one
+ * or more null elements and this list does not permit null
+ * elements, or if the specified collection is null
+ * @throws IllegalArgumentException if some property of an element of the
+ * specified collection prevents it from being added to this list
*/
- public void addAll(Collection<? extends T> collection) {
+ public void addAll(@NonNull Collection<? extends T> collection) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.addAll(collection);
@@ -235,7 +247,7 @@
* @param object The object to insert into the array.
* @param index The index at which the object must be inserted.
*/
- public void insert(T object, int index) {
+ public void insert(@Nullable T object, int index) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.add(index, object);
@@ -251,7 +263,7 @@
*
* @param object The object to remove.
*/
- public void remove(T object) {
+ public void remove(@Nullable T object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.remove(object);
@@ -282,7 +294,7 @@
* @param comparator The comparator used to sort the objects contained
* in this adapter.
*/
- public void sort(Comparator<? super T> comparator) {
+ public void sort(@NonNull Comparator<? super T> comparator) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.sort(mOriginalValues, comparator);
@@ -293,9 +305,6 @@
if (mNotifyOnChange) notifyDataSetChanged();
}
- /**
- * {@inheritDoc}
- */
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
@@ -326,21 +335,17 @@
*
* @return The Context associated with this adapter.
*/
- public Context getContext() {
+ public @NonNull Context getContext() {
return mContext;
}
- /**
- * {@inheritDoc}
- */
+ @Override
public int getCount() {
return mObjects.size();
}
- /**
- * {@inheritDoc}
- */
- public T getItem(int position) {
+ @Override
+ public @Nullable T getItem(int position) {
return mObjects.get(position);
}
@@ -351,28 +356,25 @@
*
* @return The position of the specified item.
*/
- public int getPosition(T item) {
+ public int getPosition(@Nullable T item) {
return mObjects.indexOf(item);
}
- /**
- * {@inheritDoc}
- */
+ @Override
public long getItemId(int position) {
return position;
}
- /**
- * {@inheritDoc}
- */
- public View getView(int position, View convertView, ViewGroup parent) {
+ @Override
+ public @NonNull View getView(int position, @Nullable View convertView,
+ @NonNull ViewGroup parent) {
return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
- private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent, int resource) {
- View view;
- TextView text;
+ private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position,
+ @Nullable View convertView, @NonNull ViewGroup parent, int resource) {
+ final View view;
+ final TextView text;
if (convertView == null) {
view = inflater.inflate(resource, parent, false);
@@ -387,6 +389,12 @@
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
+
+ if (text == null) {
+ throw new RuntimeException("Failed to find view with ID "
+ + mContext.getResources().getResourceName(mFieldId)
+ + " in item layout");
+ }
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
@@ -394,9 +402,9 @@
"ArrayAdapter requires the resource ID to be a TextView", e);
}
- T item = getItem(position);
+ final T item = getItem(position);
if (item instanceof CharSequence) {
- text.setText((CharSequence)item);
+ text.setText((CharSequence) item);
} else {
text.setText(item.toString());
}
@@ -426,7 +434,7 @@
* @see #getDropDownView(int, View, ViewGroup)
*/
@Override
- public void setDropDownViewTheme(Resources.Theme theme) {
+ public void setDropDownViewTheme(@Nullable Resources.Theme theme) {
if (theme == null) {
mDropDownInflater = null;
} else if (theme == mInflater.getContext().getTheme()) {
@@ -438,12 +446,13 @@
}
@Override
- public Resources.Theme getDropDownViewTheme() {
+ public @Nullable Resources.Theme getDropDownViewTheme() {
return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
}
@Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ public View getDropDownView(int position, @Nullable View convertView,
+ @NonNull ViewGroup parent) {
final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater;
return createViewFromResource(inflater, position, convertView, parent, mDropDownResource);
}
@@ -458,16 +467,14 @@
*
* @return An ArrayAdapter<CharSequence>.
*/
- public static ArrayAdapter<CharSequence> createFromResource(Context context,
+ public static @NonNull ArrayAdapter<CharSequence> createFromResource(@NonNull Context context,
@ArrayRes int textArrayResId, @LayoutRes int textViewResId) {
- CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
- return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
+ final CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
+ return new ArrayAdapter<>(context, textViewResId, strings);
}
- /**
- * {@inheritDoc}
- */
- public Filter getFilter() {
+ @Override
+ public @NonNull Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
@@ -482,31 +489,31 @@
private class ArrayFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
- FilterResults results = new FilterResults();
+ final FilterResults results = new FilterResults();
if (mOriginalValues == null) {
synchronized (mLock) {
- mOriginalValues = new ArrayList<T>(mObjects);
+ mOriginalValues = new ArrayList<>(mObjects);
}
}
if (prefix == null || prefix.length() == 0) {
- ArrayList<T> list;
+ final ArrayList<T> list;
synchronized (mLock) {
- list = new ArrayList<T>(mOriginalValues);
+ list = new ArrayList<>(mOriginalValues);
}
results.values = list;
results.count = list.size();
} else {
- String prefixString = prefix.toString().toLowerCase();
+ final String prefixString = prefix.toString().toLowerCase();
- ArrayList<T> values;
+ final ArrayList<T> values;
synchronized (mLock) {
- values = new ArrayList<T>(mOriginalValues);
+ values = new ArrayList<>(mOriginalValues);
}
final int count = values.size();
- final ArrayList<T> newValues = new ArrayList<T>();
+ final ArrayList<T> newValues = new ArrayList<>();
for (int i = 0; i < count; i++) {
final T value = values.get(i);
@@ -517,11 +524,8 @@
newValues.add(value);
} else {
final String[] words = valueText.split(" ");
- final int wordCount = words.length;
-
- // Start at index 0, in case valueText starts with space(s)
- for (int k = 0; k < wordCount; k++) {
- if (words[k].startsWith(prefixString)) {
+ for (String word : words) {
+ if (word.startsWith(prefixString)) {
newValues.add(value);
break;
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 6e3dbd8..e3357a7 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -75,8 +75,6 @@
*/
@Widget
public class DatePicker extends FrameLayout {
- private static final String LOG_TAG = DatePicker.class.getSimpleName();
-
private static final int MODE_SPINNER = 1;
private static final int MODE_CALENDAR = 2;
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 5adac01..332e89c 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -378,9 +378,9 @@
mCurrentDate.set(Calendar.MONTH, monthOfYear);
mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
- mDateChangedListener = callBack;
-
onDateChanged(false, false);
+
+ mDateChangedListener = callBack;
}
@Override
diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java
index 255de79..d8a3c56 100644
--- a/core/java/android/widget/DatePickerSpinnerDelegate.java
+++ b/core/java/android/widget/DatePickerSpinnerDelegate.java
@@ -244,6 +244,7 @@
setDate(year, monthOfYear, dayOfMonth);
updateSpinners();
updateCalendarView();
+
mOnDateChangedListener = onDateChangedListener;
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 434e3eb..ad35550 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -155,6 +155,9 @@
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SET_TEXT: {
+ if (!isEnabled()) {
+ return false;
+ }
CharSequence text = (arguments != null) ? arguments.getCharSequence(
AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
setText(text);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 59d857c..47b0348 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3640,6 +3640,9 @@
}
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
+ if (mHasSelection && !mTextView.hasTransientState()) {
+ mTextView.setHasTransientState(true);
+ }
return true;
} else {
return false;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index fe8916b..9ac4917 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -16,17 +16,15 @@
package android.widget;
-import java.util.ArrayList;
+import com.android.internal.R;
+import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -36,8 +34,7 @@
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
+import java.util.ArrayList;
/**
* FrameLayout is designed to block out an area on the screen to display
@@ -75,31 +72,29 @@
@ViewDebug.ExportedProperty(category = "padding")
private int mForegroundPaddingBottom = 0;
- private final Rect mSelfBounds = new Rect();
- private final Rect mOverlayBounds = new Rect();
-
- private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1);
-
- public FrameLayout(Context context) {
+ private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
+
+ public FrameLayout(@NonNull Context context) {
super(context);
}
-
- public FrameLayout(Context context, @Nullable AttributeSet attrs) {
+
+ public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public FrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public FrameLayout(
- Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes);
-
- if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) {
+ attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes);
+
+ if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) {
setMeasureAllChildren(true);
}
@@ -171,10 +166,6 @@
mPaddingBottom + mForegroundPaddingBottom;
}
-
- /**
- * {@inheritDoc}
- */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
@@ -264,17 +255,13 @@
}
}
}
-
- /**
- * {@inheritDoc}
- */
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
- void layoutChildren(int left, int top, int right, int bottom,
- boolean forceLeftGravity) {
+ void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
@@ -378,12 +365,9 @@
return mMeasureAllChildren;
}
- /**
- * {@inheritDoc}
- */
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new FrameLayout.LayoutParams(getContext(), attrs);
+ return new FrameLayout.LayoutParams(getContext(), attrs);
}
@Override
@@ -391,9 +375,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
@@ -431,34 +412,30 @@
* Per-child layout information for layouts that support margins.
* See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
* for a list of all child view attributes that this class supports.
- *
+ *
* @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
*/
public static class LayoutParams extends MarginLayoutParams {
/**
* The gravity to apply with the View to which these layout parameters
* are associated.
+ * <p>
+ * The default value is {@code Gravity.TOP | Gravity.START}
*
* @see android.view.Gravity
- *
* @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
*/
- public int gravity = -1;
+ public int gravity = DEFAULT_CHILD_GRAVITY;
- /**
- * {@inheritDoc}
- */
- public LayoutParams(Context c, AttributeSet attrs) {
+ public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
super(c, attrs);
- TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
- gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
+ final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout);
+ gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity,
+ DEFAULT_CHILD_GRAVITY);
a.recycle();
}
- /**
- * {@inheritDoc}
- */
public LayoutParams(int width, int height) {
super(width, height);
}
@@ -468,9 +445,9 @@
* and weight.
*
* @param width the width, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
* @param height the height, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
* @param gravity the gravity
*
* @see android.view.Gravity
@@ -480,17 +457,11 @@
this.gravity = gravity;
}
- /**
- * {@inheritDoc}
- */
- public LayoutParams(ViewGroup.LayoutParams source) {
+ public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
super(source);
}
- /**
- * {@inheritDoc}
- */
- public LayoutParams(ViewGroup.MarginLayoutParams source) {
+ public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
super(source);
}
@@ -500,7 +471,7 @@
*
* @param source The layout params to copy from.
*/
- public LayoutParams(LayoutParams source) {
+ public LayoutParams(@NonNull LayoutParams source) {
super(source);
this.gravity = source.gravity;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 92631da..c471cf3 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1214,6 +1214,7 @@
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
p.width, p.height, gravity);
updateAboveAnchor(aboveAnchor);
+ p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
invokePopup(p);
}
@@ -1984,6 +1985,13 @@
update = true;
}
+ int newAccessibilityIdOfAnchor =
+ (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1;
+ if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
+ p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
+ update = true;
+ }
+
if (update) {
setLayoutDirectionFromAnchor();
mWindowManager.updateViewLayout(mDecorView, p);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8bd63df..6d2cea6 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.ColorInt;
+import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
@@ -228,6 +229,11 @@
public boolean onClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
+ return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
+ }
+
+ public boolean onClickHandler(View view, PendingIntent pendingIntent,
+ Intent fillInIntent, int launchStackId) {
try {
// TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
Context context = view.getContext();
@@ -239,6 +245,10 @@
0, 0,
view.getMeasuredWidth(), view.getMeasuredHeight());
}
+
+ if (launchStackId != StackId.INVALID_STACK_ID) {
+ opts.setLaunchStackId(launchStackId);
+ }
context.startIntentSender(
pendingIntent.getIntentSender(), fillInIntent,
Intent.FLAG_ACTIVITY_NEW_TASK,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7602416..da0768e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8289,14 +8289,12 @@
newSelEnd = Selection.getSelectionEnd(buf);
}
- if (newSelStart == newSelEnd && hasTransientState()) {
- setHasTransientState(false);
- } else if (newSelStart != newSelEnd && !hasTransientState()) {
- setHasTransientState(true);
- }
-
if (mEditor != null) {
mEditor.refreshTextActionMode();
+ if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) {
+ // User generated selection has been removed.
+ setHasTransientState(false);
+ }
}
onSelectionChanged(newSelStart, newSelEnd);
}
@@ -9096,6 +9094,9 @@
if (mBufferType == BufferType.EDITABLE) {
info.setEditable(true);
+ if (isEnabled()) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
+ }
}
if (mEditor != null) {
@@ -9227,6 +9228,17 @@
}
}
} return false;
+ case AccessibilityNodeInfo.ACTION_SET_TEXT: {
+ if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
+ return false;
+ }
+ CharSequence text = (arguments != null) ? arguments.getCharSequence(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
+ setText(text);
+ if (text != null && text.length() > 0) {
+ Selection.setSelection((Spannable) mText, text.length());
+ }
+ } return true;
default: {
return super.performAccessibilityActionInternal(action, arguments);
}
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 36db5d7..a9d5113 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -219,7 +219,7 @@
public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) {
// We don't care about the various suggestion types, just "suggested" (!= 0)
// and "all others" (== 0)
- if (mCountryMode || (lhs.isSuggested() == rhs.isSuggested())) {
+ if (lhs.isSuggested() == rhs.isSuggested()) {
// They are in the same "bucket" (suggested / others), so we compare the text
return mCollator.compare(
removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)),
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index c4e6675..7803e52 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -31,8 +31,9 @@
private static boolean sFullyInitialized = false;
public static class LocaleInfo {
- private static final int SUGGESTION_TYPE_NONE = 0x00;
- private static final int SUGGESTION_TYPE_SIM = 0x01;
+ private static final int SUGGESTION_TYPE_NONE = 0;
+ private static final int SUGGESTION_TYPE_SIM = 1 << 0;
+ private static final int SUGGESTION_TYPE_CFG = 1 << 1;
private final Locale mLocale;
private final Locale mParent;
@@ -273,6 +274,22 @@
final HashSet<String> localizedLocales = new HashSet<>();
for (String localeId : LocalePicker.getSystemAssetLocales()) {
LocaleInfo li = new LocaleInfo(localeId);
+ final String country = li.getLocale().getCountry();
+ // All this is to figure out if we should suggest a country
+ if (!country.isEmpty()) {
+ LocaleInfo cachedLocale = null;
+ if (sLocaleCache.containsKey(li.getId())) { // the simple case, e.g. fr-CH
+ cachedLocale = sLocaleCache.get(li.getId());
+ } else { // e.g. zh-TW localized, zh-Hant-TW in cache
+ final String langScriptCtry = li.getLangScriptKey() + "-" + country;
+ if (sLocaleCache.containsKey(langScriptCtry)) {
+ cachedLocale = sLocaleCache.get(langScriptCtry);
+ }
+ }
+ if (cachedLocale != null) {
+ cachedLocale.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CFG;
+ }
+ }
localizedLocales.add(li.getLangScriptKey());
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0e02ed6..ff680e2 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -243,10 +243,6 @@
return;
}
- // Prevent the Resolver window from becoming the top fullscreen window and thus from taking
- // control of the system bars.
- getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR);
-
final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel);
if (rdl != null) {
rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 98102ea..e2d29e3 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -49,6 +49,7 @@
private static final int TYPE_HEADER_SUGGESTED = 0;
private static final int TYPE_HEADER_ALL_OTHERS = 1;
private static final int TYPE_LOCALE = 2;
+ private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6;
private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions;
private ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
@@ -171,7 +172,15 @@
}
private boolean showHeaders() {
- if (mCountryMode) { // never show suggestions in country mode
+ // We don't want to show suggestions for locales with very few regions
+ // (e.g. Romanian, with 2 regions)
+ // So we put a (somewhat) arbitrary limit.
+ //
+ // The initial idea was to make that limit dependent on the screen height.
+ // But that would mean rotating the screen could make the suggestions disappear,
+ // as the number of countries that fits on the screen would be different in portrait
+ // and landscape mode.
+ if (mCountryMode && mLocaleOptions.size() < MIN_REGIONS_FOR_SUGGESTIONS) {
return false;
}
return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size();
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index d80b63a..0f257d7 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -24,6 +24,7 @@
import android.app.SearchManager;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.IRotationWatcher.Stub;
@@ -514,6 +515,11 @@
mDecorContentParent.setWindowTitle(title);
}
mTitle = title;
+ WindowManager.LayoutParams params = getAttributes();
+ if (!TextUtils.equals(title, params.getTitle())) {
+ params.setTitle(title);
+ dispatchWindowAttributesChanged(getAttributes());
+ }
}
@Override
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index ee73b90..bed5a2e 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -238,6 +238,14 @@
return total;
}
+ public static int[] convertToIntArray(List<Integer> list) {
+ int[] array = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ array[i] = list.get(i);
+ }
+ return array;
+ }
+
/**
* Adds value to given array if not already present, providing set-like
* behavior.
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 7c85246..1ead5b3 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -191,6 +191,21 @@
* Ensures that that the argument numeric value is non-negative.
*
* @param value a numeric long value
+ * @return the validated numeric value
+ * @throws IllegalArgumentException if {@code value} was negative
+ */
+ public static long checkArgumentNonnegative(final long value) {
+ if (value < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ return value;
+ }
+
+ /**
+ * Ensures that that the argument numeric value is non-negative.
+ *
+ * @param value a numeric long value
* @param errorMessage the exception message to use if the check fails
* @return the validated numeric value
* @throws IllegalArgumentException if {@code value} was negative
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9d14478..3d892af 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1099,7 +1099,8 @@
|| mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
|| mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
|| mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
return passwordEnabled && savedPasswordExists(userId);
}
diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java
new file mode 100644
index 0000000..0b3f2ae
--- /dev/null
+++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.content.Context;
+import android.content.pm.IShortcutService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class ShortcutBackupHelper extends BlobBackupHelper {
+ private static final String TAG = "ShortcutBackupAgent";
+ private static final int BLOB_VERSION = 1;
+
+ private static final String KEY_USER_FILE = "shortcutuser.xml";
+
+ public ShortcutBackupHelper() {
+ super(BLOB_VERSION, KEY_USER_FILE);
+ }
+
+ private IShortcutService getShortcutService() {
+ return IShortcutService.Stub.asInterface(
+ ServiceManager.getService(Context.SHORTCUT_SERVICE));
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ switch (key) {
+ case KEY_USER_FILE:
+ try {
+ return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Backup failed", e);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown key: " + key);
+ }
+ return null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ switch (key) {
+ case KEY_USER_FILE:
+ try {
+ getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Restore failed", e);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown key: " + key);
+ }
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 181ed51..2d12fcd 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,9 +17,9 @@
package com.android.server.backup;
import android.app.IWallpaperManager;
+import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupAgentHelper;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.WallpaperBackupHelper;
@@ -48,6 +48,7 @@
private static final String NOTIFICATION_HELPER = "notifications";
private static final String PERMISSION_HELPER = "permissions";
private static final String USAGE_STATS_HELPER = "usage_stats";
+ private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -100,6 +101,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -138,6 +140,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 27b9830..22a81d4 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -251,13 +251,6 @@
void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes,
SkColorTable* ctable) {
- {
- android::AutoMutex _lock(mLock);
- if (mPinnedRefCount) {
- ALOGW("Called reconfigure on a bitmap that is in use! This may"
- " cause graphical corruption!");
- }
- }
mPixelRef->reconfigure(info, rowBytes, ctable);
}
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 8724729..42dc983 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -16,23 +16,39 @@
#include "context_hub.h"
+#define LOG_NDEBUG 0
+#define LOG_TAG "ContextHubService"
+
+#include <inttypes.h>
+#include <jni.h>
+#include <map>
+#include <queue>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
-#include <jni.h>
+#include <cutils/log.h>
+
#include "JNIHelp.h"
#include "core_jni_helpers.h"
-#include "stdint.h"
-#include "stdlib.h"
+
+//static constexpr int OS_APP_ID=-1;
+
+static constexpr int MIN_APP_ID=1;
+static constexpr int MAX_APP_ID=128;
+
+static constexpr size_t MSG_HEADER_SIZE=4;
+static constexpr int HEADER_FIELD_MSG_TYPE=0;
+//static constexpr int HEADER_FIELD_MSG_VERSION=1;
+static constexpr int HEADER_FIELD_HUB_HANDLE=2;
+static constexpr int HEADER_FIELD_APP_INSTANCE=3;
namespace android {
namespace {
-// TODO: We should share this array_length function widely around Android
-// code.
/*
* Finds the length of a statically-sized array using template trickery that
* also prevents it from being applied to the wrong type.
@@ -64,35 +80,207 @@
jmethodID contextHubInfoSetPeakPowerDrawMw;
jmethodID contextHubInfoSetSupportedSensors;
jmethodID contextHubInfoSetMemoryRegions;
+ jmethodID contextHubInfoSetMaxPacketLenBytes;
jmethodID contextHubServiceMsgReceiptCallback;
+ jmethodID contextHubServiceAddAppInstance;
};
struct context_hub_info_s {
- int cookie;
+ uint32_t *cookies;
int numHubs;
const struct context_hub_t *hubs;
struct context_hub_module_t *contextHubModule;
};
+struct app_instance_info_s {
+ uint32_t hubHandle; // Id of the hub this app is on
+ int instanceId; // systemwide unique instance id - assigned
+ struct hub_app_info appInfo; // returned from the HAL
+ uint64_t truncName; // Possibly truncated name - logging
+};
+
struct contextHubServiceDb_s {
int initialized;
context_hub_info_s hubInfo;
jniInfo_s jniInfo;
+ std::queue<int> freeIds;
+ std::map<int, app_instance_info_s *> appInstances;
};
} // unnamed namespace
static contextHubServiceDb_s db;
-int context_hub_callback(uint32_t hub_id, const struct hub_message_t *msg,
+int context_hub_callback(uint32_t hubId, const struct hub_message_t *msg,
void *cookie);
+const context_hub_t *get_hub_info(int hubHandle) {
+ if (hubHandle >= 0 && hubHandle < db.hubInfo.numHubs) {
+ return &db.hubInfo.hubs[hubHandle];
+ }
+ return nullptr;
+}
+
+static int send_msg_to_hub(const hub_message_t *msg, int hubHandle) {
+ const context_hub_t *info = get_hub_info(hubHandle);
+
+ if (info) {
+ return db.hubInfo.contextHubModule->send_message(info->hub_id, msg);
+ } else {
+ ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle);
+ return -1;
+ }
+}
+
+static int set_os_app_as_destination(hub_message_t *msg, int hubHandle) {
+ const context_hub_t *info = get_hub_info(hubHandle);
+
+ if (info) {
+ msg->app = info->os_app_name;
+ return 0;
+ } else {
+ ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle);
+ return -1;
+ }
+}
+
+static int get_hub_id_for_app_instance(int id) {
+ if (db.appInstances.find(id) == db.appInstances.end()) {
+ ALOGD("%s: Cannot find app for app instance %d", __FUNCTION__, id);
+ return -1;
+ }
+
+ int hubHandle = db.appInstances[id]->hubHandle;
+
+ return db.hubInfo.hubs[hubHandle].hub_id;
+}
+
+static int set_dest_app(hub_message_t *msg, int id) {
+ if (db.appInstances.find(id) == db.appInstances.end()) {
+ ALOGD("%s: Cannod find app for app instance %d", __FUNCTION__, id);
+ return -1;
+ }
+
+ msg->app = db.appInstances[id]->appInfo.name;
+ return 0;
+}
+
+static void send_query_for_apps() {
+ hub_message_t msg;
+
+ msg.message_type = CONTEXT_HUB_QUERY_APPS;
+ msg.message_len = 0;
+
+ for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
+ ALOGD("Sending query for apps to hub %d", i);
+ set_os_app_as_destination(&msg, i);
+ if (send_msg_to_hub(&msg, i) != 0) {
+ ALOGW("Could not query hub %i for apps", i);
+ }
+ }
+}
+
+static int return_id(int id) {
+ // Note : This method is not thread safe.
+ // id returned is guarenteed to be in use
+ db.freeIds.push(id);
+ return 0;
+}
+
+static int generate_id(void) {
+ // Note : This method is not thread safe.
+ int retVal = -1;
+
+ if (!db.freeIds.empty()) {
+ retVal = db.freeIds.front();
+ db.freeIds.pop();
+ }
+
+ return retVal;
+}
+
+int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, JNIEnv *env) {
+ // Not checking if the apps are indeed distinct
+
+ app_instance_info_s *entry;
+ void *appName;
+ hub_app_name_t *name;
+
+ assert(appInfo && appInfo->name && appInfo->name->app_name);
+
+ entry = (app_instance_info_s *) malloc(sizeof(app_instance_info_s));
+ appName = malloc(appInfo->name->app_name_len);
+ name = (hub_app_name_t *) malloc(sizeof(hub_app_name_t));
+
+ int appInstanceHandle = generate_id();
+
+ if (appInstanceHandle < 0 || !appName || !entry || !name) {
+ ALOGE("Cannot find resources to add app instance %d, %p, %p",
+ appInstanceHandle, appName, entry);
+
+ free(appName);
+ free(entry);
+ free(name);
+
+ if (appInstanceHandle >= 0) {
+ return_id(appInstanceHandle);
+ }
+
+ return -1;
+ }
+
+ memcpy(&(entry->appInfo), appInfo, sizeof(entry->appInfo));
+ memcpy(appName, appInfo->name->app_name, appInfo->name->app_name_len);
+ name->app_name = appName;
+ name->app_name_len = appInfo->name->app_name_len;
+ entry->appInfo.name = name;
+ entry->truncName = 0;
+ memcpy(&(entry->truncName), name->app_name,
+ sizeof(entry->truncName) < name->app_name_len ?
+ sizeof(entry->truncName) : name->app_name_len);
+
+ // Not checking for sanity of hubId
+ entry->hubHandle = hubHandle;
+ entry->instanceId = appInstanceHandle;
+ db.appInstances[appInstanceHandle] = entry;
+
+ // Finally - let the service know of this app instance
+ env->CallIntMethod(db.jniInfo.jContextHubService,
+ db.jniInfo.contextHubServiceAddAppInstance,
+ hubHandle, entry->instanceId, entry->truncName,
+ entry->appInfo.version);
+
+ ALOGW("Added App 0x%" PRIx64 " on hub Handle %" PRId32
+ " as appInstance %d, original name_length %" PRId32, entry->truncName,
+ entry->hubHandle, appInstanceHandle, name->app_name_len);
+
+ return appInstanceHandle;
+}
+
+int delete_app_instance(int id) {
+ if (db.appInstances.find(id) == db.appInstances.end()) {
+ return -1;
+ }
+
+ return_id(id);
+
+ if (db.appInstances[id]) {
+ // Losing the const cast below. This is intentional.
+ free((void *)db.appInstances[id]->appInfo.name->app_name);
+ free((void *)db.appInstances[id]->appInfo.name);
+ free(db.appInstances[id]);
+ db.appInstances.erase(id);
+ }
+
+ return 0;
+}
+
+
static void initContextHubService() {
int err = 0;
- db.hubInfo.hubs = NULL;
+ db.hubInfo.hubs = nullptr;
db.hubInfo.numHubs = 0;
- db.hubInfo.cookie = 0;
int i;
err = hw_get_module(CONTEXT_HUB_MODULE_ID,
@@ -103,26 +291,45 @@
strerror(-err));
}
- if (db.hubInfo.contextHubModule) {
- ALOGD("Fetching hub info");
- db.hubInfo.numHubs = db.hubInfo.contextHubModule->get_hubs(db.hubInfo.contextHubModule,
- &db.hubInfo.hubs);
+ // Prep for storing app info
+ for(i = MIN_APP_ID; i <= MAX_APP_ID; i++) {
+ db.freeIds.push(i);
+ }
- if (db.hubInfo.numHubs > 0) {
- for (i = 0; i < db.hubInfo.numHubs; i++) {
- // TODO : Event though one cookie is OK for now, lets change
- // this to be one per hub
- db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id,
- context_hub_callback,
- &db.hubInfo.cookie);
+ if (db.hubInfo.contextHubModule) {
+ int retNumHubs = db.hubInfo.contextHubModule->get_hubs(db.hubInfo.contextHubModule,
+ &db.hubInfo.hubs);
+ ALOGD("ContextHubModule returned %d hubs ", retNumHubs);
+ db.hubInfo.numHubs = retNumHubs;
+
+ if (db.hubInfo.numHubs > 0) {
+ db.hubInfo.numHubs = retNumHubs;
+ db.hubInfo.cookies = (uint32_t *)malloc(sizeof(uint32_t) * db.hubInfo.numHubs);
+
+ if (!db.hubInfo.cookies) {
+ ALOGW("Ran out of memory allocating cookies, bailing");
+ return;
+ }
+
+ for (i = 0; i < db.hubInfo.numHubs; i++) {
+ db.hubInfo.cookies[i] = db.hubInfo.hubs[i].hub_id;
+ if (db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id,
+ context_hub_callback,
+ &db.hubInfo.cookies[i]) == 0) {
+ }
+ }
}
- }
+
+ send_query_for_apps();
+ } else {
+ ALOGW("No Context Hub Module present");
}
}
static int onMessageReceipt(int *header, int headerLen, char *msg, int msgLen) {
JNIEnv *env;
- if ((db.jniInfo.vm)->AttachCurrentThread(&env, NULL) != JNI_OK) {
+
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
return -1;
}
@@ -132,28 +339,131 @@
env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg);
env->SetIntArrayRegion(jheader, 0, headerLen, (jint *)header);
-
- return env->CallIntMethod(db.jniInfo.jContextHubService,
+ return (env->CallIntMethod(db.jniInfo.jContextHubService,
db.jniInfo.contextHubServiceMsgReceiptCallback,
- jheader, jmsg);
+ jheader, jmsg) != 0);
}
-int context_hub_callback(uint32_t hub_id, const struct hub_message_t *msg,
+int handle_query_apps_response(char *msg, int msgLen, uint32_t hubHandle) {
+ int i;
+ JNIEnv *env;
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
+ return -1;
+ }
+
+ int numApps = msgLen/sizeof(hub_app_info);
+ hub_app_info *info = (hub_app_info *)malloc(msgLen); // handle possible alignment
+
+ if (!info) {
+ return -1;
+ }
+
+ memcpy(info, msg, msgLen);
+ for (i = 0; i < numApps; i++) {
+ add_app_instance(info, hubHandle, env);
+ info++;
+ }
+
+ free(info);
+
+ return 0;
+}
+
+
+int handle_os_message(uint32_t msgType, uint32_t hubHandle,
+ char *msg, int msgLen) {
+ int retVal;
+
+ switch(msgType) {
+ case CONTEXT_HUB_APPS_ENABLE:
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_APPS_DISABLE:
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_LOAD_APP:
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_UNLOAD_APP:
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_QUERY_APPS:
+ retVal = handle_query_apps_response(msg, msgLen, hubHandle);
+ break;
+
+ case CONTEXT_HUB_QUERY_MEMORY:
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_LOAD_OS:
+ retVal = 0;
+ break;
+
+ default:
+ retVal = -1;
+ break;
+
+ }
+
+ return retVal;
+}
+
+static bool sanity_check_cookie(void *cookie, uint32_t hub_id) {
+ int *ptr = (int *)cookie;
+
+ if (!ptr || *ptr >= db.hubInfo.numHubs) {
+ return false;
+ }
+
+ if (db.hubInfo.hubs[*ptr].hub_id != hub_id) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+int context_hub_callback(uint32_t hubId,
+ const struct hub_message_t *msg,
void *cookie) {
- int msgHeader[4];
+ int msgHeader[MSG_HEADER_SIZE];
- msgHeader[0] = msg->message_type;
- msgHeader[1] = 0; // TODO : HAL does not have a version field
- msgHeader[2] = hub_id;
+ if (!msg) {
+ return -1;
+ }
- onMessageReceipt(msgHeader, sizeof(msgHeader), (char *)msg->message, msg->message_len); // TODO : Populate this
- return 0;
+ msgHeader[HEADER_FIELD_MSG_TYPE] = msg->message_type;
+
+ if (!sanity_check_cookie(cookie, hubId)) {
+ ALOGW("Incorrect cookie %" PRId32 " for cookie %p! Bailing",
+ hubId, cookie);
+
+ return -1;
+ }
+
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = *(uint32_t*)cookie;
+
+ if (msgHeader[HEADER_FIELD_MSG_TYPE] < CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE &&
+ msgHeader[HEADER_FIELD_MSG_TYPE] != 0 ) {
+ handle_os_message(msgHeader[HEADER_FIELD_MSG_TYPE],
+ msgHeader[HEADER_FIELD_HUB_HANDLE],
+ (char *)msg->message,
+ msg->message_len);
+ } else {
+ onMessageReceipt(msgHeader, sizeof(msgHeader),
+ (char *)msg->message, msg->message_len);
+ }
+
+ return 0;
}
static int init_jni(JNIEnv *env, jobject instance) {
if (env->GetJavaVM(&db.jniInfo.vm) != JNI_OK) {
- return -1;
+ return -1;
}
db.jniInfo.jContextHubService = env->NewGlobalRef(instance);
@@ -167,7 +477,6 @@
db.jniInfo.memoryRegionsClass =
env->FindClass("android/hardware/location/MemoryRegion");
- //TODO :: Add error checking
db.jniInfo.contextHubInfoCtor =
env->GetMethodID(db.jniInfo.contextHubInfoClass, "<init>", "()V");
db.jniInfo.contextHubInfoSetId =
@@ -209,6 +518,9 @@
db.jniInfo.contextHubInfoSetMemoryRegions =
env->GetMethodID(db.jniInfo.contextHubInfoClass,
"setMemoryRegions", "([Landroid/hardware/location/MemoryRegion;)V");
+ db.jniInfo.contextHubInfoSetMaxPacketLenBytes =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setMaxPacketLenBytes", "(I)V");
db.jniInfo.contextHubServiceMsgReceiptCallback =
@@ -218,6 +530,11 @@
env->GetMethodID(db.jniInfo.contextHubInfoClass, "setName",
"(Ljava/lang/String;)V");
+ db.jniInfo.contextHubServiceAddAppInstance =
+ env->GetMethodID(db.jniInfo.contextHubServiceClass,
+ "addAppInstance", "(IIJI)I");
+
+
return 0;
}
@@ -245,20 +562,29 @@
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPlatformVersion, hub->platform_version);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchainVersion, hub->toolchain_version);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakMips, hub->peak_mips);
- env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw, hub->stopped_power_draw_mw);
- env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw, hub->sleep_power_draw_mw);
- env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw, hub->peak_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw,
+ hub->stopped_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw,
+ hub->sleep_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw,
+ hub->peak_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMaxPacketLenBytes,
+ hub->max_supported_msg_len);
+
// TODO : jintBuf = env->NewIntArray(hub->num_connected_sensors);
- // TODO : env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors, hub->connected_sensors);
+ // TODO : env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors,
+ // hub->connected_sensors);
jintBuf = env->NewIntArray(array_length(dummyConnectedSensors));
env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors, dummyConnectedSensors);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSupportedSensors, jintBuf);
// We are not getting the memory regions from the CH Hal - change this when it is available
- jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, NULL);
+ jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, nullptr);
// Note the zero size above. We do not need to set any elements
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMemoryRegions, jmemBuf);
+
return jHub;
}
@@ -267,18 +593,18 @@
jobject hub;
jobjectArray retArray;
- initContextHubService();
-
if (init_jni(env, instance) < 0) {
- return NULL;
+ return nullptr;
}
- // Note : The service is clamping the number of hubs to 1
- db.hubInfo.numHubs = 1;
-
initContextHubService();
- retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, NULL);
+ if (db.hubInfo.numHubs > 1) {
+ ALOGW("Clamping the number of hubs to 1");
+ db.hubInfo.numHubs = 1;
+ }
+
+ retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, nullptr);
for(int i = 0; i < db.hubInfo.numHubs; i++) {
hub = constructJContextHubInfo(env, &db.hubInfo.hubs[i]);
@@ -291,28 +617,27 @@
static jint nativeSendMessage(JNIEnv *env, jobject instance, jintArray header_,
jbyteArray data_) {
hub_message_t msg;
- hub_app_name_t dest;
- uint8_t os_name[8];
-
- memset(os_name, 0, sizeof(os_name));
+ jint retVal = -1; // Default to failure
jint *header = env->GetIntArrayElements(header_, 0);
- //int numHeaderElements = env->GetArrayLength(header_);
+ unsigned int numHeaderElements = env->GetArrayLength(header_);
jbyte *data = env->GetByteArrayElements(data_, 0);
int dataBufferLength = env->GetArrayLength(data_);
- /* Assume an int - thats all we understand */
- dest.app_name_len = array_length(os_name); // TODO : Check this
- //dest.app_name = &header[1];
- dest.app_name = os_name;
-
- msg.app = &dest;
-
- msg.message_type = header[3];
- msg.message_len = dataBufferLength;
- msg.message = data;
-
- jint retVal = db.hubInfo.contextHubModule->send_message(header[0], &msg);
+ if (numHeaderElements >= MSG_HEADER_SIZE) {
+ if (set_dest_app(&msg, header[HEADER_FIELD_APP_INSTANCE]) == 0) {
+ msg.message_type = header[HEADER_FIELD_MSG_TYPE];
+ msg.message_len = dataBufferLength;
+ msg.message = data;
+ retVal = db.hubInfo.contextHubModule->send_message(
+ get_hub_id_for_app_instance(header[HEADER_FIELD_APP_INSTANCE]),
+ &msg);
+ } else {
+ ALOGD("Could not find app instance %d", header[HEADER_FIELD_APP_INSTANCE]);
+ }
+ } else {
+ ALOGD("Malformed header len");
+ }
env->ReleaseIntArrayElements(header_, header, 0);
env->ReleaseByteArrayElements(data_, data, 0);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0a4ef98..30a5993 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -845,14 +845,14 @@
-->
<permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
- android:protectionLevel="dangerous"/>
+ android:protectionLevel="signatureOrSystem"/>
<!-- @hide Allows an application to Access UCE-OPTIONS.
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
- android:protectionLevel="dangerous"/>
+ android:protectionLevel="signatureOrSystem"/>
@@ -2004,6 +2004,11 @@
<permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows but does not guarantee access to user passwords at the conclusion of add
+ account -->
+ <permission android:name="android.permission.GET_PASSWORD_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows applications to RW to diagnostic resources.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.DIAGNOSTIC"
@@ -2984,6 +2989,11 @@
<permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Allows an application to whitelist tasks during lock task mode
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
+ android:protectionLevel="signature|setup" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d1c0895..5b4364d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -225,6 +225,9 @@
granted any application pre-installed on the system image (not just privileged
apps). -->
<flag name="preinstalled" value="0x400" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the setup wizard app -->
+ <flag name="setup" value="0x800" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index f779d6c..ecf88f1 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -675,4 +675,26 @@
assertFloatingToolbarContainsItem(
getActivity().getString(com.android.internal.R.string.copy));
}
+
+ @SmallTest
+ public void testTransientState() throws Exception {
+ final String text = "abc def";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ assertFalse(textView.hasTransientState());
+
+ onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b')));
+ // hasTransientState should return true when user generated selection is active.
+ assertTrue(textView.hasTransientState());
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d')));
+ // hasTransientState should return false as the selection has been cleared.
+ assertFalse(textView.hasTransientState());
+ textView.post(
+ () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
+ getInstrumentation().waitForIdleSync();
+ // hasTransientState should return false when selection is created by API.
+ assertFalse(textView.hasTransientState());
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
index b8ea2de..bec4180 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -21,7 +21,6 @@
import android.support.test.espresso.UiController;
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.MotionEvents.DownResultHolder;
import android.support.test.espresso.action.Press;
@@ -34,7 +33,7 @@
* ViewAction for performing an click on View by a mouse.
*/
public final class MouseClickAction implements ViewAction {
- private final GeneralClickAction mGeneralClickAction;
+ private final ViewClickAction mViewClickAction;
@MouseUiController.MouseButton
private final int mButton;
@@ -100,30 +99,22 @@
*/
public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
@MouseUiController.MouseButton int button) {
- mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider, Press.PINPOINT);
+ mViewClickAction = new ViewClickAction(tapper, coordinatesProvider, Press.PINPOINT);
mButton = button;
}
@Override
public Matcher<View> getConstraints() {
- return mGeneralClickAction.getConstraints();
+ return mViewClickAction.getConstraints();
}
@Override
public String getDescription() {
- return mGeneralClickAction.getDescription();
+ return mViewClickAction.getDescription();
}
@Override
public void perform(UiController uiController, View view) {
- mGeneralClickAction.perform(new MouseUiController(uiController, mButton), view);
- long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
- if (0 < doubleTapTimeout) {
- // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be
- // detected as a triple click. e.g. 2 double clicks are detected as a triple click and
- // a single click because espresso isn't aware of triple click detection logic, which
- // is TextView specific gesture.
- uiController.loopMainThreadForAtLeast(doubleTapTimeout);
- }
+ mViewClickAction.perform(new MouseUiController(uiController, mButton), view);
}
}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 4cecb65..335d021 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -17,15 +17,10 @@
package android.widget.espresso;
import static android.support.test.espresso.action.ViewActions.actionWithAssertions;
-
-import org.hamcrest.core.Is;
-import org.hamcrest.core.IsInstanceOf;
-
import android.graphics.Rect;
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.Press;
import android.support.test.espresso.action.Tap;
import android.support.test.espresso.util.HumanReadables;
@@ -55,7 +50,7 @@
*/
public static ViewAction clickOnTextAtIndex(int index) {
return actionWithAssertions(
- new GeneralClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
+ new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
}
/**
@@ -101,7 +96,7 @@
*/
public static ViewAction doubleClickOnTextAtIndex(int index) {
return actionWithAssertions(
- new GeneralClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
+ new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER));
}
/**
@@ -131,7 +126,7 @@
*/
public static ViewAction longPressOnTextAtIndex(int index) {
return actionWithAssertions(
- new GeneralClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
+ new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
}
/**
diff --git a/core/tests/coretests/src/android/widget/espresso/ViewClickAction.java b/core/tests/coretests/src/android/widget/espresso/ViewClickAction.java
new file mode 100644
index 0000000..8bce1b0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/ViewClickAction.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.UiController;
+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.PrecisionDescriber;
+import android.support.test.espresso.action.Tapper;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+public final class ViewClickAction implements ViewAction {
+ private final GeneralClickAction mGeneralClickAction;
+
+ public ViewClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
+ PrecisionDescriber precisionDescriber) {
+ mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
+ precisionDescriber);
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return mGeneralClickAction.getConstraints();
+ }
+
+ @Override
+ public String getDescription() {
+ return mGeneralClickAction.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ mGeneralClickAction.perform(uiController, view);
+ long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ if (0 < doubleTapTimeout) {
+ // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be
+ // detected as a double click or triple click. e.g. 2 double clicks on TextView are
+ // detected as a triple click and a single click because espresso isn't aware of
+ // TextView specific gestures.
+ uiController.loopMainThreadForAtLeast(doubleTapTimeout);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
index 7519627..2a24881 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java
@@ -22,7 +22,6 @@
import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.KeyEvent;
-import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 0a8c692..00eff91 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2157,7 +2157,7 @@
final RecordConfigChangeCallbackData cbData =
(RecordConfigChangeCallbackData) msg.obj;
if (cbData.mCb != null) {
- cbData.mCb.onRecordConfigChanged(cbData.mConfigs);
+ cbData.mCb.onRecordingConfigChanged(cbData.mConfigs);
}
break;
default:
@@ -2746,7 +2746,7 @@
* @param configs array containing the results of
* {@link AudioManager#getActiveRecordingConfigurations()}.
*/
- public void onRecordConfigChanged(AudioRecordingConfiguration[] configs) {}
+ public void onRecordingConfigChanged(AudioRecordingConfiguration[] configs) {}
}
private static class AudioRecordingCallbackInfo {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b6ff41e..621129d 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -596,14 +596,14 @@
* AudioTrack player = new AudioTrack.Builder()
* .setAudioAttributes(new AudioAttributes.Builder()
* .setUsage(AudioAttributes.USAGE_ALARM)
- * .setContentType(CONTENT_TYPE_MUSIC)
+ * .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
* .build())
* .setAudioFormat(new AudioFormat.Builder()
* .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
- * .setSampleRate(441000)
+ * .setSampleRate(44100)
* .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
* .build())
- * .setBufferSize(minBuffSize)
+ * .setBufferSizeInBytes(minBuffSize)
* .build();
* </pre>
* <p>
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index b1c1b79..2bd9781 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2666,12 +2666,13 @@
public static final int HEVCHighTierLevel62 = 0x2000000;
// from OMX_VIDEO_DOLBYVISIONPROFILETYPE
- public static final int DolbyVisionProfileDvavDer = 0x1;
- public static final int DolbyVisionProfileDvavDen = 0x2;
- public static final int DolbyVisionProfileDvheDer = 0x3;
- public static final int DolbyVisionProfileDvheDen = 0x4;
- public static final int DolbyVisionProfileDvheDtr = 0x5;
- public static final int DolbyVisionProfileDvheStn = 0x6;
+ public static final int DolbyVisionProfileDvavPer = 0x1;
+ public static final int DolbyVisionProfileDvavPen = 0x2;
+ public static final int DolbyVisionProfileDvheDer = 0x4;
+ public static final int DolbyVisionProfileDvheDen = 0x8;
+ public static final int DolbyVisionProfileDvheDtr = 0x10;
+ public static final int DolbyVisionProfileDvheStn = 0x20;
+ public static final int DolbyVisionProfileDvheDth = 0x40;
// from OMX_VIDEO_DOLBYVISIONLEVELTYPE
public static final int DolbyVisionLevelHd24 = 0x1;
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 177344a..24a400e4 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -275,16 +275,23 @@
return initDataMap.get(schemeUuid);
}
};
- } else if (formatMap.containsKey("crypto-key")) {
- ByteBuffer buf = (ByteBuffer) formatMap.get("crypto-key");
- buf.rewind();
- final byte[] data = new byte[buf.remaining()];
- buf.get(data);
- return new DrmInitData() {
- public SchemeInitData get(UUID schemeUuid) {
- return new DrmInitData.SchemeInitData("webm", data);
+ } else {
+ int numTracks = getTrackCount();
+ for (int i = 0; i < numTracks; ++i) {
+ Map<String, Object> trackFormatMap = getTrackFormatNative(i);
+ if (!trackFormatMap.containsKey("crypto-key")) {
+ continue;
}
- };
+ ByteBuffer buf = (ByteBuffer) trackFormatMap.get("crypto-key");
+ buf.rewind();
+ final byte[] data = new byte[buf.remaining()];
+ buf.get(data);
+ return new DrmInitData() {
+ public SchemeInitData get(UUID schemeUuid) {
+ return new DrmInitData.SchemeInitData("webm", data);
+ }
+ };
+ }
}
return null;
}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index c1805cb..7c6adad 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -72,7 +72,7 @@
/**
* Used as an int extra field to denote the page number to subscribe.
- * The value of {@code EXTRA_PAGE} should be greater than or equal to 1.
+ * The value of {@code EXTRA_PAGE} should be greater than or equal to 0.
*
* @see #EXTRA_PAGE_SIZE
*/
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
index b06e598..2943e60 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -50,7 +50,7 @@
startIndex1 = 0;
endIndex1 = Integer.MAX_VALUE;
} else {
- startIndex1 = pageSize1 * (page1 - 1);
+ startIndex1 = pageSize1 * page1;
endIndex1 = startIndex1 + pageSize1 - 1;
}
@@ -58,7 +58,7 @@
startIndex2 = 0;
endIndex2 = Integer.MAX_VALUE;
} else {
- startIndex2 = pageSize2 * (page2 - 1);
+ startIndex2 = pageSize2 * page2;
endIndex2 = startIndex2 + pageSize2 - 1;
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index acf94f4c..03dc699 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -116,10 +116,10 @@
private final int mType;
private final boolean mIsHardwareInput;
- // TODO: Remove mLabel and mIconUri when createTvInputInfo() is removed.
- private String mLabel;
+ // TODO: Remove mIconUri when createTvInputInfo() is removed.
private Uri mIconUri;
+ private final CharSequence mLabel;
private final int mLabelResId;
private final Icon mIcon;
private final Icon mIconStandby;
@@ -161,8 +161,8 @@
TvInputInfo info = new TvInputInfo.Builder(context, service)
.setHdmiDeviceInfo(hdmiDeviceInfo)
.setParentId(parentId)
+ .setLabel(label)
.build();
- info.mLabel = label;
info.mIconUri = iconUri;
return info;
}
@@ -215,8 +215,8 @@
throws XmlPullParserException, IOException {
TvInputInfo info = new TvInputInfo.Builder(context, service)
.setTvInputHardwareInfo(hardwareInfo)
+ .setLabel(label)
.build();
- info.mLabel = label;
info.mIconUri = iconUri;
return info;
}
@@ -247,7 +247,7 @@
}
private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
- int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
+ CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
String setupActivity, String settingsActivity, boolean canRecord, int tunerCount,
HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId,
Bundle extras) {
@@ -255,6 +255,7 @@
mId = id;
mType = type;
mIsHardwareInput = isHardwareInput;
+ mLabel = label;
mLabelResId = labelResId;
mIcon = icon;
mIconStandby = iconStandby;
@@ -570,7 +571,7 @@
dest.writeString(mId);
dest.writeInt(mType);
dest.writeByte(mIsHardwareInput ? (byte) 1 : 0);
- dest.writeString(mLabel);
+ TextUtils.writeToParcel(mLabel, dest, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeInt(mLabelResId);
dest.writeParcelable(mIcon, flags);
@@ -612,7 +613,7 @@
mId = in.readString();
mType = in.readInt();
mIsHardwareInput = in.readByte() == 1;
- mLabel = in.readString();
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mIconUri = in.readParcelable(null);
mLabelResId = in.readInt();
mIcon = in.readParcelable(null);
@@ -660,6 +661,7 @@
private final Context mContext;
private final ResolveInfo mResolveInfo;
+ private CharSequence mLabel;
private int mLabelResId;
private Icon mIcon;
private Icon mIconStandby;
@@ -746,12 +748,31 @@
/**
* Sets the label.
*
+ * @param label The text to be used as label.
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ * @hide
+ */
+ @SystemApi
+ public Builder setLabel(CharSequence label) {
+ if (mLabelResId != 0) {
+ throw new IllegalStateException("Resource ID for label is already set.");
+ }
+ this.mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets the label.
+ *
* @param resId The resource ID of the text to use.
* @return This Builder object to allow for chaining of calls to builder methods.
* @hide
*/
@SystemApi
public Builder setLabel(int resId) {
+ if (mLabel != null) {
+ throw new IllegalStateException("Label text is already set.");
+ }
this.mLabelResId = resId;
return this;
}
@@ -868,8 +889,8 @@
type = TYPE_TUNER;
}
parseServiceMetadata(type);
- return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabelResId, mIcon,
- mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity,
+ return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
+ mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity,
mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index ae86632..6954045 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -657,9 +657,9 @@
if (page == -1 && pageSize == -1) {
return list;
}
- int fromIndex = pageSize * (page - 1);
+ int fromIndex = pageSize * page;
int toIndex = fromIndex + pageSize;
- if (page < 1 || pageSize < 1 || fromIndex >= list.size()) {
+ if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
return Collections.EMPTY_LIST;
}
if (toIndex > list.size()) {
diff --git a/packages/CtsShim/Android.mk b/packages/CtsShim/Android.mk
new file mode 100644
index 0000000..537b171
--- /dev/null
+++ b/packages/CtsShim/Android.mk
@@ -0,0 +1,61 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+###########################################################
+# Variant: Privileged app
+
+include $(CLEAR_VARS)
+# this needs to be a privileged application
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PACKAGE_NAME := CtsShimPriv
+
+#TODO need to find the correct certificate
+#Change in conjunction with cts/hostsidetests/appsecurity/test-apps/IntentFilterApp
+LOCAL_CERTIFICATE := platform
+LOCAL_MANIFEST_FILE := priv_shim/AndroidManifest.xml
+
+include $(BUILD_PACKAGE)
+
+
+
+###########################################################
+# Variant: System app
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PACKAGE_NAME := CtsShim
+
+#TODO need to find the correct certificate
+#Change in conjunction with cts/hostsidetests/appsecurity/test-apps/IntentFilterApp
+LOCAL_CERTIFICATE := platform
+LOCAL_MANIFEST_FILE := shim/AndroidManifest.xml
+
+include $(BUILD_PACKAGE)
+
+
diff --git a/packages/CtsShim/priv_shim/AndroidManifest.xml b/packages/CtsShim/priv_shim/AndroidManifest.xml
new file mode 100644
index 0000000..0a3f823
--- /dev/null
+++ b/packages/CtsShim/priv_shim/AndroidManifest.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Manifest for the privileged CTS shim -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.priv.ctsshim">
+ <application android:label="CtsShim">
+
+ <!-- These activities don't actually exist; define them just to test the filters !-->
+
+ <!-- install test; [some] high priority filters granted -->
+ <activity android:name=".InstallPriority">
+ <!-- normal actions; priority will be granted -->
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEARCH" />
+ <category android:name="android.intent.category.INFO" />
+ </intent-filter>
+
+ <!-- protected actions; priority will be denied -->
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEND" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SENDTO" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; single, equivalent filter -->
+ <activity android:name=".UpgradeMatch">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.MATCH" />
+ <category android:name="android.intent.category.INFO" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; multiple, equivalent filters -->
+ <activity android:name=".UpgradeMatchMultiple">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.MATCH_MULTIPLE" />
+ <category android:name="android.intent.category.INFO" />
+ </intent-filter>
+
+ <intent-filter android:priority="150">
+ <action android:name="com.android.cts.action.MATCH_MULTIPLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="www.google.com" android:port="80" />
+ <data android:host="www.google.com" android:port="8080" />
+ <data android:host="goo.gl" android:port="443" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; lower priority -->
+ <activity android:name=".UpgradeLowerPriority">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.LOWER_PRIORITY" />
+ <category android:name="android.intent.category.INFO" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; action subset -->
+ <activity android:name=".UpgradeActionSubset">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.ACTION_SUB" />
+ <action android:name="com.android.cts.action.ACTION_SUB_2" />
+ <action android:name="com.android.cts.action.ACTION_SUB_3" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; category subset -->
+ <activity android:name=".UpgradeCategorySubset">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.CATEGORY_SUB" />
+ <category android:name="android.intent.category.INFO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; scheme subset -->
+ <activity android:name=".UpgradeSchemeSubset">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.SCHEME_SUB" />
+ <data android:scheme="content" />
+ <data android:scheme="flubber" />
+ <data android:scheme="zoodle" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; authority subset -->
+ <activity android:name=".UpgradeAuthoritySubset">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.AUTHORITY_SUB" />
+ <data android:host="www.google.com" android:port="80" />
+ <data android:host="www.google.com" android:port="8080" />
+ <data android:host="mail.google.com" android:port="80" />
+ <data android:host="goo.gl" android:port="443" />
+ </intent-filter>
+ </activity>
+
+
+ <!-- upgrade test; new action -->
+ <activity android:name=".UpgradeNewAction">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.NEW_ACTION" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; new category -->
+ <activity android:name=".UpgradeNewCategory">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.NEW_CATEGORY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; new scheme -->
+ <activity android:name=".UpgradeNewScheme">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.NEW_SCHEME" />
+ <data android:scheme="content" />
+ </intent-filter>
+ </activity>
+
+ <!-- upgrade test; new authority -->
+ <activity android:name=".UpgradeNewAuthority">
+ <intent-filter android:priority="100">
+ <action android:name="com.android.cts.action.NEW_AUTHORITY" />
+ <data android:host="www.google.com" android:port="80" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
+
diff --git a/packages/CtsShim/shim/AndroidManifest.xml b/packages/CtsShim/shim/AndroidManifest.xml
new file mode 100644
index 0000000..ee4b547
--- /dev/null
+++ b/packages/CtsShim/shim/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Manifest for the system CTS shim -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.system.ctsshim">
+ <application android:label="CtsShim">
+
+ <!-- These activities don't actually exist; define them just to test the filters !-->
+
+ <!-- install test; high priority filter DENIED -->
+ <activity android:name=".InstallPriority">
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEARCH" />
+ <category android:name="android.intent.category.INFO" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEND" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ </intent-filter>
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.SENDTO" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
+
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 70b478a..87136ef3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -272,6 +272,7 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ Metrics.logMenuAction(this, item.getItemId());
switch (item.getItemId()) {
case android.R.id.home:
@@ -635,10 +636,12 @@
return true;
}
} else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ Metrics.logKeyboardAction(this, keyCode);
// Tab toggles focus on the navigation drawer.
toggleNavDrawerFocus();
return true;
} else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ Metrics.logKeyboardAction(this, keyCode);
popDir();
return true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 68c0c2a..d4439d8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -212,19 +212,22 @@
case R.id.menu_create_dir:
assert(canCreateDirectory());
showCreateDirectoryDialog();
- return true;
+ break;
case R.id.menu_new_window:
createNewWindow();
- return true;
+ break;
case R.id.menu_paste_from_clipboard:
DirectoryFragment dir = getDirectoryFragment();
if (dir != null) {
dir.pasteFromClipboard();
}
- return true;
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
}
- return super.onOptionsItemSelected(item);
+ Metrics.logMenuAction(this, item.getItemId());
+ return true;
}
private void createNewWindow() {
@@ -346,18 +349,21 @@
case KeyEvent.KEYCODE_A:
dir = getDirectoryFragment();
if (dir != null) {
+ Metrics.logKeyboardAction(this, keyCode);
dir.selectAllFiles();
}
return true;
case KeyEvent.KEYCODE_C:
dir = getDirectoryFragment();
if (dir != null) {
+ Metrics.logKeyboardAction(this, keyCode);
dir.copySelectedToClipboard();
}
return true;
case KeyEvent.KEYCODE_V:
dir = getDirectoryFragment();
if (dir != null) {
+ Metrics.logKeyboardAction(this, keyCode);
dir.pasteFromClipboard();
}
return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index f1f47c8..a4a67f9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;
+import android.view.KeyEvent;
import com.android.documentsui.State.ActionType;
import com.android.documentsui.model.DocumentInfo;
@@ -67,6 +68,10 @@
private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
+ private static final String COUNT_DRAG_N_DROP = "docsui_drag_n_drop";
+ private static final String COUNT_SEARCH = "docsui_search";
+ private static final String COUNT_MENU_ACTION = "docsui_menu_action";
+ private static final String COUNT_KEYBOARD_ACTION = "docsui_keyboard_action";
// Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
// root that is not explicitly recognized by the Metrics code (see {@link
@@ -196,8 +201,71 @@
@Retention(RetentionPolicy.SOURCE)
public @interface MetricsOpType {}
- // Codes representing different launch actions. These are used for bucketing stats in the
- // COUNT_LAUNCH_ACTION histogram.
+ // Codes representing different provider types. Used for sorting file operations when logging.
+ private static final int PROVIDER_INTRA = 0;
+ private static final int PROVIDER_SYSTEM = 1;
+ private static final int PROVIDER_EXTERNAL = 2;
+
+ @IntDef(flag = false, value = {
+ PROVIDER_INTRA,
+ PROVIDER_SYSTEM,
+ PROVIDER_EXTERNAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Provider {}
+
+
+ // Codes representing different menu actions. These are used for bucketing stats in the
+ // COUNT_MENU_ACTION histogram.
+ // Both regular toolbar menu and action mode menu operations are included.
+ // Do not change or rearrange these values, that will break historical data. Only add to the
+ // list.
+ // Do not use negative numbers or zero; clearcut only handles positive integers.
+ private static final int ACTION_MENU_OTHER = 1;
+ private static final int ACTION_MENU_GRID = 2;
+ private static final int ACTION_MENU_LIST = 3;
+ private static final int ACTION_MENU_SORT = 4;
+ private static final int ACTION_MENU_SORT_NAME = 5;
+ private static final int ACTION_MENU_SORT_DATE = 6;
+ private static final int ACTION_MENU_SORT_SIZE = 7;
+ private static final int ACTION_MENU_SEARCH = 8;
+ private static final int ACTION_MENU_SHOW_SIZE = 9;
+ private static final int ACTION_MENU_SETTINGS = 10;
+ private static final int ACTION_MENU_COPY_TO = 11;
+ private static final int ACTION_MENU_MOVE_TO = 12;
+ private static final int ACTION_MENU_DELETE = 13;
+ private static final int ACTION_MENU_RENAME = 14;
+ private static final int ACTION_MENU_CREATE_DIR = 15;
+ private static final int ACTION_MENU_SELECT_ALL = 16;
+ private static final int ACTION_MENU_SHARE = 17;
+ private static final int ACTION_MENU_OPEN = 18;
+ private static final int ACTION_MENU_ADVANCED = 19;
+
+ @IntDef(flag = false, value = {
+ ACTION_MENU_OTHER,
+ ACTION_MENU_GRID,
+ ACTION_MENU_LIST,
+ ACTION_MENU_SORT,
+ ACTION_MENU_SORT_NAME,
+ ACTION_MENU_SORT_DATE,
+ ACTION_MENU_SORT_SIZE,
+ ACTION_MENU_SHOW_SIZE,
+ ACTION_MENU_SETTINGS,
+ ACTION_MENU_COPY_TO,
+ ACTION_MENU_MOVE_TO,
+ ACTION_MENU_DELETE,
+ ACTION_MENU_RENAME,
+ ACTION_MENU_CREATE_DIR,
+ ACTION_MENU_SELECT_ALL,
+ ACTION_MENU_SHARE,
+ ACTION_MENU_OPEN,
+ ACTION_MENU_ADVANCED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MenuAction {}
+
+ // Codes representing different menu actions. These are used for bucketing stats in the
+ // COUNT_MENU_ACTION histogram.
// Do not change or rearrange these values, that will break historical data. Only add to the
// list.
// Do not use negative numbers or zero; clearcut only handles positive integers.
@@ -223,18 +291,30 @@
@Retention(RetentionPolicy.SOURCE)
public @interface MetricsAction {}
- // Codes representing different provider types. Used for sorting file operations when logging.
- private static final int PROVIDER_INTRA = 0;
- private static final int PROVIDER_SYSTEM = 1;
- private static final int PROVIDER_EXTERNAL = 2;
+ // Codes representing different keyboard shortcut triggered actions. These are used for
+ // bucketing stats in the COUNT_KEYBOARD_ACTION histogram.
+ // Do not change or rearrange these values, that will break historical data. Only add to the
+ // list.
+ // Do not use negative numbers or zero; clearcut only handles positive integers.
+ private static final int ACTION_KEYBOARD_OTHER = 1;
+ private static final int ACTION_KEYBOARD_PASTE = 2;
+ private static final int ACTION_KEYBOARD_COPY = 3;
+ private static final int ACTION_KEYBOARD_DELETE = 4;
+ private static final int ACTION_KEYBOARD_SELECT_ALL = 5;
+ private static final int ACTION_KEYBOARD_BACK = 6;
+ private static final int ACTION_KEYBOARD_SWITCH_FOCUS = 7;
- @IntDef(flag = true, value = {
- PROVIDER_INTRA,
- PROVIDER_SYSTEM,
- PROVIDER_EXTERNAL
+ @IntDef(flag = false, value = {
+ ACTION_KEYBOARD_OTHER,
+ ACTION_KEYBOARD_PASTE,
+ ACTION_KEYBOARD_COPY,
+ ACTION_KEYBOARD_DELETE,
+ ACTION_KEYBOARD_SELECT_ALL,
+ ACTION_KEYBOARD_BACK,
+ ACTION_KEYBOARD_SWITCH_FOCUS
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Provider {}
+ public @interface KeyboardAction {}
// Codes representing different actions to open the drawer. They are used for bucketing stats in
// the COUNT_DRAWER_OPENED histogram.
@@ -440,6 +520,39 @@
}
/**
+ * Logs keyboard shortcut actions. Since keyboard shortcuts have their corresponding menu items,
+ * they are identified by menu item resource id for convenience.
+ * @param context
+ * @param keyCode
+ */
+ public static void logKeyboardAction(Context context, int keyCode) {
+ @KeyboardAction int keyboardAction = ACTION_KEYBOARD_OTHER;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_V:
+ keyboardAction = ACTION_KEYBOARD_PASTE;
+ break;
+ case KeyEvent.KEYCODE_C:
+ keyboardAction = ACTION_KEYBOARD_COPY;
+ break;
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ keyboardAction = ACTION_KEYBOARD_DELETE;
+ break;
+ case KeyEvent.KEYCODE_A:
+ keyboardAction = ACTION_KEYBOARD_SELECT_ALL;
+ break;
+ case KeyEvent.KEYCODE_DEL:
+ keyboardAction = ACTION_KEYBOARD_BACK;
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ keyboardAction = ACTION_KEYBOARD_SWITCH_FOCUS;
+ break;
+ default:
+ break;
+ }
+ logHistogram(context, COUNT_KEYBOARD_ACTION, keyboardAction);
+ }
+
+ /**
* Logs startup time in milliseconds.
* @param context
* @param startupMs Startup time in milliseconds.
@@ -448,6 +561,25 @@
logHistogram(context, COUNT_STARTUP_MS, startupMs);
}
+ /**
+ * Logs a drag and drop action. Call this when the user drops the content triggering copy.
+ * operation.
+ *
+ * @param context
+ */
+ public static void logDragNDrop(Context context) {
+ logCount(context, COUNT_DRAG_N_DROP);
+ }
+
+ /**
+ * Logs a search. Call this when the search operation is finished.
+ *
+ * @param context
+ */
+ public static void logSearch(Context context) {
+ logCount(context, COUNT_SEARCH);
+ }
+
private static void logInterProviderFileOps(
Context context,
String histogram,
@@ -568,6 +700,74 @@
}
/**
+ * Logs menu action that was selected by user.
+ * @param context
+ * @param id Resource id of the menu item.
+ */
+ public static void logMenuAction(Context context, int id) {
+ @MenuAction int menuAction = ACTION_MENU_OTHER;
+ switch (id) {
+ case R.id.menu_grid:
+ menuAction = ACTION_MENU_GRID;
+ break;
+ case R.id.menu_list:
+ menuAction = ACTION_MENU_LIST;
+ break;
+ case R.id.menu_sort:
+ menuAction = ACTION_MENU_SORT;
+ break;
+ case R.id.menu_sort_name:
+ menuAction = ACTION_MENU_SORT_NAME;
+ break;
+ case R.id.menu_sort_date:
+ menuAction = ACTION_MENU_SORT_DATE;
+ break;
+ case R.id.menu_sort_size:
+ menuAction = ACTION_MENU_SORT_SIZE;
+ break;
+ case R.id.menu_search:
+ menuAction = ACTION_MENU_SEARCH;
+ break;
+ case R.id.menu_file_size:
+ menuAction = ACTION_MENU_SHOW_SIZE;
+ break;
+ case R.id.menu_settings:
+ menuAction = ACTION_MENU_SETTINGS;
+ break;
+ case R.id.menu_copy_to:
+ menuAction = ACTION_MENU_COPY_TO;
+ break;
+ case R.id.menu_move_to:
+ menuAction = ACTION_MENU_MOVE_TO;
+ break;
+ case R.id.menu_delete:
+ menuAction = ACTION_MENU_DELETE;
+ break;
+ case R.id.menu_rename:
+ menuAction = ACTION_MENU_RENAME;
+ break;
+ case R.id.menu_create_dir:
+ menuAction = ACTION_MENU_CREATE_DIR;
+ break;
+ case R.id.menu_select_all:
+ menuAction = ACTION_MENU_SELECT_ALL;
+ break;
+ case R.id.menu_share:
+ menuAction = ACTION_MENU_SHARE;
+ break;
+ case R.id.menu_open:
+ menuAction = ACTION_MENU_OPEN;
+ break;
+ case R.id.menu_advanced:
+ menuAction = ACTION_MENU_ADVANCED;
+ break;
+ default:
+ break;
+ }
+ logHistogram(context, COUNT_MENU_ACTION, menuAction);
+ }
+
+ /**
* Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
*
* @param context
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
index 11b8891..945ed34 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
@@ -185,6 +185,9 @@
if(mFullBar) {
Menu menu = mActionBar.getMenu();
menu.setGroupVisible(R.id.group_hide_when_searching, false);
+ } else {
+ // If search in full-bar mode it will be logged in FilesActivity#onOptionsItemSelected
+ Metrics.logMenuAction(mActionBar.getContext(), R.id.menu_search);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 73ce0e1..20316ff 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -85,6 +85,7 @@
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.Menus;
import com.android.documentsui.MessageBar;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.RecentsLoader;
import com.android.documentsui.RootsCache;
@@ -594,6 +595,7 @@
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ Metrics.logMenuAction(getContext(), item.getItemId());
Selection selection = mSelectionManager.getSelection(new Selection());
@@ -1124,6 +1126,7 @@
if (Objects.equals(src, dst)) {
return false;
}
+ Metrics.logDragNDrop(getContext());
copyFromClipData(event.getClipData(), dst);
return true;
}
@@ -1336,6 +1339,7 @@
// This has to be handled here instead of in a keyboard shortcut, because
// keyboard shortcuts all have to be modified with the 'Ctrl' key.
if (mSelectionManager.hasSelection()) {
+ Metrics.logKeyboardAction(getContext(), keyCode);
deleteDocuments(mSelectionManager.getSelection());
}
// Always handle the key, even if there was nothing to delete. This is a
@@ -1622,6 +1626,9 @@
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
if (!isAdded()) return;
+ if (mSearchMode) {
+ Metrics.logSearch(getContext());
+ }
State state = getDisplayState();
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
index 454221a..2b549f1 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -77,6 +77,7 @@
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
return SecurityMode.Password;
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java
index af4c347..0feda92 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java
@@ -244,9 +244,20 @@
ranges = PageRangeUtils.normalize(ranges);
+ int lastPageIdx = mEditor.getPageCount() - 1;
+
final int rangeCount = ranges.length;
for (int i = rangeCount - 1; i >= 0; i--) {
PageRange range = ranges[i];
+
+ // Ignore removal of pages that are outside the document
+ if (range.getEnd() > lastPageIdx) {
+ if (range.getStart() > lastPageIdx) {
+ continue;
+ }
+ range = new PageRange(range.getStart(), lastPageIdx);
+ }
+
for (int j = range.getEnd(); j >= range.getStart(); j--) {
mEditor.removePage(j);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index dba6a8b..cde0fa3 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -476,7 +476,7 @@
}
private void onPrintDocumentError(String message) {
- mProgressMessageController.cancel();
+ setState(mProgressMessageController.cancel());
ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY);
setState(STATE_UPDATE_FAILED);
@@ -502,7 +502,7 @@
Log.i(LOG_TAG, "onUpdateCanceled()");
}
- mProgressMessageController.cancel();
+ setState(mProgressMessageController.cancel());
ensurePreviewUiShown();
switch (mState) {
@@ -524,7 +524,7 @@
Log.i(LOG_TAG, "onUpdateCompleted()");
}
- mProgressMessageController.cancel();
+ setState(mProgressMessageController.cancel());
ensurePreviewUiShown();
// Update the print job with the info for the written document. The page
@@ -572,7 +572,7 @@
Log.i(LOG_TAG, "onUpdateFailed()");
}
- mProgressMessageController.cancel();
+ setState(mProgressMessageController.cancel());
ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
if (mState == STATE_CREATE_FILE_FAILED
@@ -1169,6 +1169,18 @@
doFinish();
}
+ /**
+ * Update the selected pages from the text field.
+ */
+ private void updateSelectedPagesFromTextField() {
+ PageRange[] selectedPages = computeSelectedPages();
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+ // Update preview.
+ updatePrintPreviewController(false);
+ }
+ }
+
private void confirmPrint() {
setState(STATE_PRINT_CONFIRMED);
@@ -1178,14 +1190,10 @@
addCurrentPrinterToHistory();
setUserPrinted();
- PageRange[] selectedPages = computeSelectedPages();
- if (!Arrays.equals(mSelectedPages, selectedPages)) {
- mSelectedPages = selectedPages;
- // Update preview.
- updatePrintPreviewController(false);
- }
-
+ // updateSelectedPagesFromTextField migth update the preview, hence apply the preview first
updateSelectedPagesFromPreview();
+ updateSelectedPagesFromTextField();
+
mPrintPreviewController.closeOptions();
if (canUpdateDocument()) {
@@ -1485,6 +1493,10 @@
cancelPrint();
}
} else if (view == mMoreOptionsButton) {
+ // The selected pages is only applied once the user leaves the text field. A click
+ // on this button, does not count as leaving.
+ updateSelectedPagesFromTextField();
+
if (mCurrentPrinter != null) {
startAdvancedPrintOptionsActivity(mCurrentPrinter);
}
@@ -2066,8 +2078,9 @@
mSpoolerProvider.destroy();
}
+ setState(mProgressMessageController.cancel());
+
if (mState != STATE_INITIALIZING) {
- mProgressMessageController.cancel();
mPrintedDocument.finish();
mPrintedDocument.destroy();
mPrintPreviewController.destroy(new Runnable() {
@@ -2773,13 +2786,7 @@
}
if (view == mPageRangeEditText && !hasFocus) {
- PageRange[] selectedPages = computeSelectedPages();
- if (selectedPages != null && !Arrays.equals(mSelectedPages, selectedPages)) {
- mSelectedPages = selectedPages;
-
- // Update preview.
- updatePrintPreviewController(false);
- }
+ updateSelectedPagesFromTextField();
}
}
}
@@ -2910,29 +2917,50 @@
private boolean mPosted;
+ /** State before run was executed */
+ private int mPreviousState = -1;
+
public ProgressMessageController(Context context) {
mHandler = new Handler(context.getMainLooper(), null, false);
}
public void post() {
- if (mPosted) {
+ if (mState == STATE_UPDATE_SLOW) {
+ setState(STATE_UPDATE_SLOW);
+ ensureProgressUiShown();
+ updateOptionsUi();
+
+ return;
+ } else if (mPosted) {
return;
}
+ mPreviousState = -1;
mPosted = true;
mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
}
- public void cancel() {
+ private int getStateAfterCancel() {
+ if (mPreviousState == -1) {
+ return mState;
+ } else {
+ return mPreviousState;
+ }
+ }
+
+ public int cancel() {
if (!mPosted) {
- return;
+ return getStateAfterCancel();
}
mPosted = false;
mHandler.removeCallbacks(this);
+
+ return getStateAfterCancel();
}
@Override
public void run() {
mPosted = false;
+ mPreviousState = mState;
setState(STATE_UPDATE_SLOW);
ensureProgressUiShown();
updateOptionsUi();
@@ -3081,12 +3109,10 @@
List<PageRange> rangesToShred = new ArrayList<>();
PageRange previousRange = null;
- final int pageCount = printJob.getDocumentInfo().getPageCount();
-
PageRange[] printedPages = printJob.getPages();
final int rangeCount = printedPages.length;
for (int i = 0; i < rangeCount; i++) {
- PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount);
+ PageRange range = printedPages[i];
if (previousRange == null) {
final int startPageIdx = 0;
@@ -3105,11 +3131,8 @@
}
if (i == rangeCount - 1) {
- final int startPageIdx = range.getEnd() + 1;
- final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1;
- if (startPageIdx <= endPageIdx) {
- PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
- rangesToShred.add(removedRange);
+ if (range.getEnd() != Integer.MAX_VALUE) {
+ rangesToShred.add(new PageRange(range.getEnd() + 1, Integer.MAX_VALUE));
}
}
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png
deleted file mode 100644
index 7b0fcc7..0000000
--- a/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png
deleted file mode 100644
index 73e9c96..0000000
--- a/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png
deleted file mode 100644
index a02e21c..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png
deleted file mode 100644
index 4af2617..0000000
--- a/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png
deleted file mode 100644
index 24bdbb6..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png
deleted file mode 100644
index 6ecd2d3..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png
deleted file mode 100644
index 5e733ef..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png
deleted file mode 100644
index ecc2c83..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable/stat_notify_image.xml b/packages/SystemUI/res/drawable/stat_notify_image.xml
new file mode 100644
index 0000000..c8745d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_notify_image.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M21,19V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14C20.1,21,21,20.1,21,19z
+M8.5,13.5l2.5,3l3.5-4.5l4.5,6H5 L8.5,13.5z" />
+ <path
+ android:pathData="M0,0h24v24H0V0z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_notify_image_error.xml b/packages/SystemUI/res/drawable/stat_notify_image_error.xml
new file mode 100644
index 0000000..b929005
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_notify_image_error.xml
@@ -0,0 +1,30 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0,0h24v24H0V0z" />
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M17,18H5l3.5-4.5l2.5,3l3.3-4.5l2.7,3.8V8h4V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h12V18z" />
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19.2,21H21v-1.8h-1.8V21z M19.2,9.9v7.4H21V9.9H19.2z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_customize_tile_divider.xml b/packages/SystemUI/res/layout/qs_customize_tile_divider.xml
new file mode 100644
index 0000000..0d932ac
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_customize_tile_divider.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:background="?android:attr/listDivider"
+ android:importantForAccessibility="no" />
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 98a1c23..57a1a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -37,7 +37,7 @@
private final int mTileSpacingPx;
private int mTilePaddingTopPx;
- private TextView mLabel;
+ protected TextView mLabel;
private ImageView mPadLock;
public QSTileView(Context context, QSIconView icon) {
@@ -81,7 +81,7 @@
FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
}
- private void createLabel() {
+ protected void createLabel() {
final Resources res = mContext.getResources();
View view = LayoutInflater.from(mContext).inflate(R.layout.qs_tile_label, null);
mLabel = (TextView) view.findViewById(R.id.tile_label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java
new file mode 100644
index 0000000..e512f93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSIconView;
+import com.android.systemui.qs.QSTileView;
+import libcore.util.Objects;
+
+public class CustomizeTileView extends QSTileView {
+
+ private TextView mAppLabel;
+
+ public CustomizeTileView(Context context, QSIconView icon) {
+ super(context, icon);
+ }
+
+ @Override
+ protected void createLabel() {
+ super.createLabel();
+ View view = LayoutInflater.from(mContext).inflate(R.layout.qs_tile_label, null);
+ mAppLabel = (TextView) view.findViewById(R.id.tile_label);
+ mAppLabel.setAlpha(.6f);
+ mAppLabel.setSingleLine(true);
+ addView(view);
+ }
+
+ public void setShowAppLabel(boolean showAppLabel) {
+ mAppLabel.setVisibility(showAppLabel ? View.VISIBLE : View.GONE);
+ mLabel.setSingleLine(showAppLabel);
+ }
+
+ public void setAppLabel(CharSequence label) {
+ if (!Objects.equal(label, mAppLabel.getText())) {
+ mAppLabel.setText(label);
+ }
+ }
+
+ public TextView getAppLabel() {
+ return mAppLabel;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 4c13451..2ba4044 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -41,7 +41,6 @@
import com.android.internal.logging.MetricsProto;
import com.android.systemui.R;
import com.android.systemui.qs.QSIconView;
-import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.customize.TileAdapter.Holder;
import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
@@ -61,6 +60,10 @@
private static final int TYPE_TILE = 0;
private static final int TYPE_EDIT = 1;
private static final int TYPE_ACCESSIBLE_DROP = 2;
+ private static final int TYPE_DIVIDER = 4;
+
+ private static final long EDIT_ID = 10000;
+ private static final long DIVIDER_ID = 20000;
private final Context mContext;
@@ -68,7 +71,8 @@
private final List<TileInfo> mTiles = new ArrayList<>();
private final ItemTouchHelper mItemTouchHelper;
private final AccessibilityManager mAccessibilityManager;
- private int mDividerIndex;
+ private int mEditIndex;
+ private int mTileDividerIndex;
private boolean mNeedsFocus;
private List<String> mCurrentSpecs;
private List<TileInfo> mOtherTiles;
@@ -87,7 +91,8 @@
@Override
public long getItemId(int position) {
- return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position)) : -1;
+ return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position))
+ : position == mEditIndex ? EDIT_ID : DIVIDER_ID;
}
public ItemTouchHelper getItemTouchHelper() {
@@ -131,8 +136,19 @@
}
}
mTiles.add(null);
+ for (int i = 0; i < mOtherTiles.size(); i++) {
+ final TileInfo tile = mOtherTiles.get(i);
+ if (tile.isSystem) {
+ mOtherTiles.remove(i--);
+ mTiles.add(tile);
+ }
+ }
+ if (mOtherTiles.size() != 0) {
+ mTileDividerIndex = mTiles.size();
+ mTiles.add(null);
+ }
mTiles.addAll(mOtherTiles);
- mDividerIndex = mTiles.indexOf(null);
+ mEditIndex = mTiles.indexOf(null);
notifyDataSetChanged();
}
@@ -147,9 +163,12 @@
@Override
public int getItemViewType(int position) {
- if (mAccessibilityMoving && position == mDividerIndex - 1) {
+ if (mAccessibilityMoving && position == mEditIndex - 1) {
return TYPE_ACCESSIBLE_DROP;
}
+ if (position == mTileDividerIndex) {
+ return TYPE_DIVIDER;
+ }
if (mTiles.get(position) == null) {
return TYPE_EDIT;
}
@@ -160,12 +179,15 @@
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
final Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
+ if (viewType == TYPE_DIVIDER) {
+ return new Holder(inflater.inflate(R.layout.qs_customize_tile_divider, parent, false));
+ }
if (viewType == TYPE_EDIT) {
return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
}
FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
false);
- frame.addView(new QSTileView(context, new QSIconView(context)));
+ frame.addView(new CustomizeTileView(context, new QSIconView(context)));
return new Holder(frame);
}
@@ -176,6 +198,9 @@
@Override
public void onBindViewHolder(final Holder holder, final int position) {
+ if (holder.getItemViewType() == TYPE_DIVIDER) {
+ return;
+ }
if (holder.getItemViewType() == TYPE_EDIT) {
((TextView) holder.itemView.findViewById(android.R.id.title)).setText(
mCurrentDrag != null ? R.string.drag_to_remove_tiles
@@ -213,7 +238,7 @@
TileInfo info = mTiles.get(position);
- if (position > mDividerIndex) {
+ if (position > mEditIndex) {
info.state.contentDescription = mContext.getString(
R.string.accessibility_qs_edit_add_tile_label, info.state.label);
} else if (mAccessibilityMoving) {
@@ -224,9 +249,11 @@
R.string.accessibility_qs_edit_tile_label, position + 1, info.state.label);
}
holder.mTileView.onStateChanged(info.state);
+ holder.mTileView.setAppLabel(info.appLabel);
+ holder.mTileView.setShowAppLabel(position > mTileDividerIndex);
if (mAccessibilityManager.isTouchExplorationEnabled()) {
- final boolean selectable = !mAccessibilityMoving || position < mDividerIndex;
+ final boolean selectable = !mAccessibilityMoving || position < mEditIndex;
holder.mTileView.setClickable(selectable);
holder.mTileView.setFocusable(selectable);
holder.mTileView.setImportantForAccessibility(selectable
@@ -239,7 +266,7 @@
if (mAccessibilityMoving) {
selectPosition(position, v);
} else {
- if (position < mDividerIndex) {
+ if (position < mEditIndex) {
showAccessibilityDialog(position, v);
} else {
startAccessibleDrag(position);
@@ -253,7 +280,7 @@
private void selectPosition(int position, View v) {
// Remove the placeholder.
- mTiles.remove(mDividerIndex--);
+ mTiles.remove(mEditIndex--);
mAccessibilityMoving = false;
move(mAccessibilityFromIndex, position, v);
notifyDataSetChanged();
@@ -272,7 +299,7 @@
if (which == 0) {
startAccessibleDrag(position);
} else {
- move(position, mDividerIndex, v);
+ move(position, mEditIndex, v);
}
}
}).setNegativeButton(android.R.string.cancel, null)
@@ -287,7 +314,7 @@
mNeedsFocus = true;
mAccessibilityFromIndex = position;
// Add placeholder for last slot.
- mTiles.add(mDividerIndex++, null);
+ mTiles.add(mEditIndex++, null);
notifyDataSetChanged();
}
@@ -296,25 +323,38 @@
}
private boolean move(int from, int to, View v) {
- if (to > mDividerIndex) {
- if (from >= mDividerIndex) {
+ if (to >= mEditIndex) {
+ if (from >= mEditIndex) {
return false;
}
+ // Sort tiles into system/non-system groups.
+ TileInfo tile = mTiles.get(from);
+ if (tile.isSystem) {
+ if (to > mTileDividerIndex) {
+ to = mTileDividerIndex;
+ }
+ } else {
+ if (mTileDividerIndex == mTiles.size()) {
+ mTiles.add(null);
+ }
+ if (to <= mTileDividerIndex) {
+ to = mTileDividerIndex;
+ }
+ }
}
CharSequence fromLabel = mTiles.get(from).state.label;
move(from, to, mTiles);
- mDividerIndex = mTiles.indexOf(null);
- notifyItemChanged(from);
- notifyItemMoved(from, to);
+ notifyDataSetChanged();
+ updateDividerLocations();
CharSequence announcement;
- if (to >= mDividerIndex) {
+ if (to >= mEditIndex) {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
strip(mTiles.get(to)));
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
from);
announcement = mContext.getString(R.string.accessibility_qs_edit_tile_removed,
fromLabel);
- } else if (from >= mDividerIndex) {
+ } else if (from >= mEditIndex) {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
strip(mTiles.get(to)));
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
@@ -333,6 +373,25 @@
return true;
}
+ private void updateDividerLocations() {
+ // The first null is the edit tiles label, the second null is the tile divider.
+ // If there is no second null, then there are no non-system tiles.
+ mEditIndex = -1;
+ mTileDividerIndex = mTiles.size();
+ for (int i = 0; i < mTiles.size(); i++) {
+ if (mTiles.get(i) == null) {
+ if (mEditIndex == -1) {
+ mEditIndex = i;
+ } else {
+ mTileDividerIndex = i;
+ }
+ }
+ }
+ if (mTiles.get(mTiles.size() - 1) == null) {
+ mTiles.remove(mTiles.size() - 1);
+ }
+ }
+
private String strip(TileInfo tileInfo) {
String spec = tileInfo.spec;
if (spec.startsWith(CustomTile.PREFIX)) {
@@ -348,12 +407,12 @@
}
public class Holder extends ViewHolder {
- private QSTileView mTileView;
+ private CustomizeTileView mTileView;
public Holder(View itemView) {
super(itemView);
if (itemView instanceof FrameLayout) {
- mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
+ mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0);
mTileView.setBackground(null);
mTileView.getIcon().disableAnimation();
}
@@ -367,6 +426,9 @@
mTileView.findViewById(R.id.tile_label).animate()
.setDuration(DRAG_LENGTH)
.alpha(0);
+ mTileView.getAppLabel().animate()
+ .setDuration(DRAG_LENGTH)
+ .alpha(0);
}
public void stopDrag() {
@@ -377,13 +439,17 @@
mTileView.findViewById(R.id.tile_label).animate()
.setDuration(DRAG_LENGTH)
.alpha(1);
+ mTileView.getAppLabel().animate()
+ .setDuration(DRAG_LENGTH)
+ .alpha(.6f);
}
}
private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
- return getItemViewType(position) == TYPE_EDIT ? 3 : 1;
+ final int type = getItemViewType(position);
+ return type == TYPE_EDIT || type == TYPE_DIVIDER ? 3 : 1;
}
};
@@ -401,7 +467,7 @@
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final ViewHolder holder = parent.getChildViewHolder(child);
- if (holder.getAdapterPosition() < mDividerIndex) {
+ if (holder.getAdapterPosition() < mEditIndex) {
continue;
}
@@ -443,7 +509,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- notifyItemChanged(mDividerIndex);
+ notifyItemChanged(mEditIndex);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index bbc8856..d04a2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -31,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.DrawableIcon;
+import com.android.systemui.qs.QSTile.State;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -79,7 +80,7 @@
mainHandler.post(new Runnable() {
@Override
public void run() {
- addTile(spec, state);
+ addTile(spec, null, state, true);
mListener.onTilesChanged(mTiles);
}
});
@@ -103,28 +104,33 @@
mListener = listener;
}
- private void addTile(String spec, QSTile.State state) {
+ private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
if (mSpecs.contains(spec)) {
return;
}
TileInfo info = new TileInfo();
info.state = state;
info.spec = spec;
+ info.appLabel = appLabel;
+ info.isSystem = isSystem;
mTiles.add(info);
mSpecs.add(spec);
}
- private void addTile(String spec, Drawable drawable, CharSequence label, Context context) {
+ private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel,
+ Context context) {
QSTile.State state = new QSTile.State();
state.label = label;
state.contentDescription = label;
state.icon = new DrawableIcon(drawable);
- addTile(spec, state);
+ addTile(spec, appLabel, state, false);
}
public static class TileInfo {
public String spec;
+ public CharSequence appLabel;
public QSTile.State state;
+ public boolean isSystem;
}
private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileInfo>> {
@@ -147,7 +153,8 @@
icon.setTint(mContext.getColor(android.R.color.white));
}
CharSequence label = info.serviceInfo.loadLabel(pm);
- addTile(spec, icon, label != null ? label.toString() : "null", mContext);
+ final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
+ addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
}
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index fff7d78..8060e07 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -344,7 +344,7 @@
// Update the nav bar scrim, but defer the animation until the enter-window event
boolean animateNavBarScrim = !launchState.launchedViaDockGesture;
- updateNavBarScrim(animateNavBarScrim, null);
+ mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null);
// If this is a new instance relaunched by AM, without going through the normal mechanisms,
// then we have to manually trigger the enter animation state
@@ -417,39 +417,43 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- // Update the nav bar for the current orientation
- updateNavBarScrim(false /* animateNavBarScrim */, AnimationProps.IMMEDIATE);
// Notify of the config change
int newOrientation = getResources().getConfiguration().orientation;
+ int numStackTasks = mRecentsView.getStack().getStackTaskCount();
EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
- (mLastOrientation != newOrientation)));
+ (mLastOrientation != newOrientation), numStackTasks > 0));
mLastOrientation = newOrientation;
}
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
+
+ // Reload the task stack completely
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1 /* topTaskId */, false /* isTopTaskHome */);
+
+ RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
+ loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
+ loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
+ loader.loadTasks(this, loadPlan, loadOpts);
+
+ TaskStack stack = loadPlan.getTaskStack();
+ int numStackTasks = stack.getStackTaskCount();
+
EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
- false /* fromOrientationChange */));
+ false /* fromOrientationChange */, numStackTasks > 0));
if (mRecentsView != null) {
- // Reload the task stack completely
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* topTaskId */, false /* isTopTaskHome */);
-
- RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
- loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
- loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
-
- mRecentsView.updateStack(loadPlan.getTaskStack());
+ mRecentsView.updateStack(stack);
}
- EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode));
+ EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode,
+ numStackTasks > 0));
}
@Override
@@ -729,18 +733,4 @@
});
return true;
}
-
- /**
- * Updates the nav bar scrim.
- */
- private void updateNavBarScrim(boolean animateNavBarScrim, AnimationProps animation) {
- // Animate the SystemUI scrims into view
- SystemServicesProxy ssp = Recents.getSystemServices();
- int taskCount = mRecentsView.getStack().getTaskCount();
- boolean hasNavBarScrim = (taskCount > 0) && !ssp.hasTransposedNavBar();
- mScrimViews.prepareEnterRecentsAnimation(hasNavBarScrim, animateNavBarScrim);
- if (animateNavBarScrim && animation != null) {
- mScrimViews.animateNavBarScrimVisibility(true, animation);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 43d627d..4dae746 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -584,18 +584,24 @@
Rect systemInsets = new Rect();
ssp.getStableInsets(systemInsets);
Rect windowRect = ssp.getWindowRect();
+ // When docked, the nav bar insets are consumed and the activity is measured without insets.
+ // However, the window bounds include the insets, so we need to subtract them here to make
+ // them identical.
+ if (ssp.hasDockedTask()) {
+ windowRect.bottom -= systemInsets.bottom;
+ systemInsets.bottom = 0;
+ }
calculateWindowStableInsets(systemInsets, windowRect);
windowRect.offsetTo(0, 0);
TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
- stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
- mTaskStackBounds);
// Rebind the header bar and draw it for the transition
- Rect taskStackBounds = new Rect(mTaskStackBounds);
stackLayout.setSystemInsets(systemInsets);
if (stack != null) {
- stackLayout.initialize(windowRect, taskStackBounds,
+ stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+ mTaskStackBounds);
+ stackLayout.initialize(windowRect, mTaskStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
}
@@ -794,6 +800,9 @@
synchronized (mHeaderBarLock) {
int toHeaderWidth = (int) toTransform.rect.width();
int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+ if (toHeaderWidth <= 0 || toHeaderHeight <= 0) {
+ return null;
+ }
boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
(int) toTransform.rect.height());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
index 8be9ca7..e3bc2a7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
@@ -25,9 +25,12 @@
public final boolean fromMultiWindow;
public final boolean fromOrientationChange;
+ public final boolean hasStackTasks;
- public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange) {
+ public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange,
+ boolean hasStackTasks) {
this.fromMultiWindow = fromMultiWindow;
this.fromOrientationChange = fromOrientationChange;
+ this.hasStackTasks = hasStackTasks;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
index 19245d9..cf2a68e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
@@ -24,8 +24,10 @@
public class MultiWindowStateChangedEvent extends EventBus.Event {
public final boolean inMultiWindow;
+ public final boolean hasStackTasks;
- public MultiWindowStateChangedEvent(boolean inMultiWindow) {
+ public MultiWindowStateChangedEvent(boolean inMultiWindow, boolean hasStackTasks) {
this.inMultiWindow = inMultiWindow;
+ this.hasStackTasks = hasStackTasks;
}
}
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 13ad9a5..07a1d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -16,40 +16,58 @@
package com.android.systemui.recents.views;
-import android.app.Activity;
import android.content.Context;
import android.view.View;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
+import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
- Context mContext;
+ private static final int DEFAULT_ANIMATION_DURATION = 150;
- View mNavBarScrimView;
+ private Context mContext;
- boolean mHasNavBarScrim;
- boolean mShouldAnimateNavBarScrim;
+ private View mNavBarScrimView;
- int mNavBarScrimEnterDuration;
+ private boolean mHasNavBarScrim;
+ private boolean mShouldAnimateNavBarScrim;
- public SystemBarScrimViews(Activity activity) {
+ private int mNavBarScrimEnterDuration;
+
+ public SystemBarScrimViews(RecentsActivity activity) {
mContext = activity;
mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
+ mNavBarScrimView.forceHasOverlappingRendering(false);
mNavBarScrimEnterDuration = activity.getResources().getInteger(
R.integer.recents_nav_bar_scrim_enter_duration);
}
/**
+ * Updates the nav bar scrim.
+ */
+ public void updateNavBarScrim(boolean animateNavBarScrim, boolean hasStackTasks,
+ AnimationProps animation) {
+ prepareEnterRecentsAnimation(isNavBarScrimRequired(hasStackTasks), animateNavBarScrim);
+ if (animateNavBarScrim && animation != null) {
+ animateNavBarScrimVisibility(true, animation);
+ }
+ }
+
+ /**
* Prepares the scrim views for animating when entering Recents. This will be called before
* the first draw, unless we are updating the scrim on configuration change.
*/
- public void prepareEnterRecentsAnimation(boolean hasNavBarScrim, boolean animateNavBarScrim) {
+ private void prepareEnterRecentsAnimation(boolean hasNavBarScrim, boolean animateNavBarScrim) {
mHasNavBarScrim = hasNavBarScrim;
mShouldAnimateNavBarScrim = animateNavBarScrim;
@@ -60,7 +78,7 @@
/**
* Animates the nav bar scrim visibility.
*/
- public void animateNavBarScrimVisibility(boolean visible, AnimationProps animation) {
+ private void animateNavBarScrimVisibility(boolean visible, AnimationProps animation) {
int toY = 0;
if (visible) {
mNavBarScrimView.setVisibility(View.VISIBLE);
@@ -79,6 +97,14 @@
}
}
+ /**
+ * @return Whether to show the nav bar scrim.
+ */
+ private boolean isNavBarScrimRequired(boolean hasStackTasks) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ return hasStackTasks && !ssp.hasTransposedNavBar() && !ssp.hasDockedTask();
+ }
+
/**** EventBus events ****/
/**
@@ -101,21 +127,48 @@
*/
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
if (mHasNavBarScrim) {
- AnimationProps animation = new AnimationProps()
- .setDuration(AnimationProps.BOUNDS,
- TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION)
- .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
+ AnimationProps animation = createBoundsAnimation(
+ TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION);
animateNavBarScrimVisibility(false, animation);
}
}
public final void onBusEvent(DismissAllTaskViewsEvent event) {
if (mHasNavBarScrim) {
- AnimationProps animation = new AnimationProps()
- .setDuration(AnimationProps.BOUNDS,
- TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION)
- .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
+ AnimationProps animation = createBoundsAnimation(
+ TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION);
animateNavBarScrimVisibility(false, animation);
}
}
+
+ public final void onBusEvent(ConfigurationChangedEvent event) {
+ animateScrimToCurrentNavBarState(event.hasStackTasks);
+ }
+
+ public final void onBusEvent(MultiWindowStateChangedEvent event) {
+ animateScrimToCurrentNavBarState(event.hasStackTasks);
+ }
+
+ /**
+ * Animates the scrim to match the state of the current nav bar.
+ */
+ private void animateScrimToCurrentNavBarState(boolean hasStackTasks) {
+ boolean hasNavBarScrim = isNavBarScrimRequired(hasStackTasks);
+ if (mHasNavBarScrim != hasNavBarScrim) {
+ AnimationProps animation = hasNavBarScrim
+ ? createBoundsAnimation(DEFAULT_ANIMATION_DURATION)
+ : AnimationProps.IMMEDIATE;
+ animateNavBarScrimVisibility(hasNavBarScrim, animation);
+ }
+ mHasNavBarScrim = hasNavBarScrim;
+ }
+
+ /**
+ * @return a default animation to aniamte the bounds of the scrim.
+ */
+ private AnimationProps createBoundsAnimation(int duration) {
+ return new AnimationProps()
+ .setDuration(AnimationProps.BOUNDS, duration)
+ .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 2508304..9eab0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -537,11 +537,13 @@
} else {
mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
}
+ mInitialNormX = null;
} else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
// If there is one stack task, ignore the min/max/initial scroll positions
mMinScrollP = 0;
mMaxScrollP = 0;
mInitialScrollP = 0;
+ mInitialNormX = null;
} else {
// Set the max scroll to be the point where the front most task is visible with the
// stack bottom offset
@@ -803,8 +805,9 @@
public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform) {
Rect windowRect = Recents.getSystemServices().getWindowRect();
- TaskViewTransform transform = getStackTransform(task, stackScroll, transformOut,
- frontTransform);
+ TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
+ transformOut, frontTransform, true /* forceUpdate */,
+ false /* ignoreTaskOverrides */);
transform.rect.offset(windowRect.left, windowRect.top);
return transform;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 3eeabc7..e5ac0d3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -141,9 +141,9 @@
return;
}
+ int viewWidth = mTaskViewRect.width();
+ int viewHeight = mTaskViewRect.height();
if (mBitmapShader != null) {
- int viewWidth = mTaskViewRect.width();
- int viewHeight = mTaskViewRect.height();
// We are drawing the thumbnail in the same orientation, so just fit the width
int thumbnailWidth = (int) (mThumbnailRect.width() * mThumbnailScale);
@@ -180,6 +180,9 @@
canvas.restoreToCount(count);
}
+ } else {
+ canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
+ mBgFillPaint);
}
}
@@ -319,12 +322,12 @@
mDisabledInSafeMode = disabledInSafeMode;
if (t.thumbnail != null) {
setThumbnail(t.thumbnail, thumbnailInfo);
- if (t.colorBackground != 0) {
- mBgFillPaint.setColor(t.colorBackground);
- }
} else {
setThumbnail(null, null);
}
+ if (t.colorBackground != 0) {
+ mBgFillPaint.setColor(t.colorBackground);
+ }
}
/** Unbinds the thumbnail view from the task */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 0e21517..4ed6426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -19,7 +19,9 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -347,7 +349,7 @@
}, afterKeyguardGone);
return true;
} else {
- return super.onClickHandler(view, pendingIntent, fillInIntent);
+ return superOnClickHandler(view, pendingIntent, fillInIntent);
}
}
@@ -384,7 +386,8 @@
private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
- return super.onClickHandler(view, pendingIntent, fillInIntent);
+ return super.onClickHandler(view, pendingIntent, fillInIntent,
+ StackId.FULLSCREEN_WORKSPACE_STACK_ID);
}
private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
@@ -990,7 +993,7 @@
}
TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
- .startActivities(null,
+ .startActivities(getActivityOptions(),
new UserHandle(UserHandle.getUserId(appUid)));
overrideActivityPendingAppTransition(keyguardShowing);
} catch (RemoteException e) {
@@ -1740,7 +1743,7 @@
} catch (RemoteException e) {
}
try {
- intent.send();
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -1848,7 +1851,8 @@
}
}
try {
- intent.send();
+ intent.send(null, 0, null, null, null, null,
+ getActivityOptions());
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -1924,6 +1928,14 @@
}
}
+ protected Bundle getActivityOptions() {
+ // Anything launched from the notification shade should always go into the
+ // fullscreen stack.
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ return options.toBundle();
+ }
+
protected void visibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
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 4c5c843..c563eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -21,7 +21,9 @@
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -3091,8 +3093,8 @@
null, mContext.getBasePackageName(),
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null,
- UserHandle.CURRENT.getIdentifier());
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+ getActivityOptions(), UserHandle.CURRENT.getIdentifier());
} catch (RemoteException e) {
Log.w(TAG, "Unable to start activity", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
index f432808..c6e4356 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java
@@ -35,11 +35,11 @@
private static final Typeface MEDIUM = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final Context mContext;
- private final LayoutInflater mInflater;
+ protected final LayoutInflater mInflater;
private final SpTexts mSpTexts;
private Callback mCallback;
- private Object mSelectedValue;
+ protected Object mSelectedValue;
public SegmentedButtons(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -65,13 +65,21 @@
final Object tag = c.getTag();
final boolean selected = Objects.equals(mSelectedValue, tag);
c.setSelected(selected);
- c.setTypeface(selected ? MEDIUM : REGULAR);
+ setSelectedStyle(c, selected);
}
fireOnSelected(fromClick);
}
+ protected void setSelectedStyle(TextView textView, boolean selected) {
+ textView.setTypeface(selected ? MEDIUM : REGULAR);
+ }
+
+ public Button inflateButton() {
+ return (Button) mInflater.inflate(R.layout.segmented_button, this, false);
+ }
+
public void addButton(int labelResId, int contentDescriptionResId, Object value) {
- final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false);
+ final Button b = inflateButton();
b.setTag(LABEL_RES_KEY, labelResId);
b.setText(labelResId);
b.setContentDescription(getResources().getString(contentDescriptionResId));
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4b96e7a..e256ecd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1262,7 +1262,7 @@
Service service = componentNameToServiceMap.get(componentName);
// Ignore non-encryption-aware services until user is unlocked
- if (!isUnlocked && !installedService.isEncryptionAware()) {
+ if (!isUnlocked && !installedService.isDirectBootAware()) {
Slog.d(LOG_TAG, "Ignoring non-encryption-aware service " + componentName);
continue;
}
@@ -1784,10 +1784,15 @@
userState.mEnabledServices.contains(userState.mServiceChangingSoftKeyboardMode);
if (!serviceChangingSoftKeyboardModeIsEnabled) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- 0,
- userState.mUserId);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ 0,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
userState.mSoftKeyboardShowMode = 0;
userState.mServiceChangingSoftKeyboardMode = null;
}
@@ -3491,6 +3496,8 @@
reportedWindow.setLayer(window.layer);
reportedWindow.setFocused(window.focused);
reportedWindow.setBoundsInScreen(window.boundsInScreen);
+ reportedWindow.setTitle(window.title);
+ reportedWindow.setAnchorId(window.accessibilityIdOfAnchor);
final int parentId = findWindowIdLocked(window.parentToken);
if (parentId >= 0) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 799a763..0a814ab 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -92,6 +92,7 @@
private static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60;
private static final int MESSAGE_TIMEOUT_BIND = 100;
private static final int MESSAGE_TIMEOUT_UNBIND = 101;
+ private static final int MESSAGE_GET_NAME_AND_ADDRESS = 200;
private static final int MESSAGE_USER_SWITCHED = 300;
private static final int MESSAGE_USER_UNLOCKED = 301;
private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
@@ -599,8 +600,8 @@
sendEnableMsg(true);
}
return true;
-
}
+
public boolean enable() {
if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
(!checkIfCallerIsForegroundUser())) {
@@ -763,9 +764,8 @@
sendEnableMsg(mQuietEnableExternal);
} else if (!isNameAndAddressSet()) {
if (DBG) Slog.d(TAG, "Getting adapter name and address");
- enable();
- waitForOnOff(true, false);
- disable(true);
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
}
}
@@ -1076,6 +1076,8 @@
private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();
private class BluetoothHandler extends Handler {
+ boolean mGetNameAddressOnly = false;
+
public BluetoothHandler(Looper looper) {
super(looper);
}
@@ -1084,6 +1086,37 @@
public void handleMessage(Message msg) {
if (DBG) Slog.d (TAG, "Message: " + msg.what);
switch (msg.what) {
+ case MESSAGE_GET_NAME_AND_ADDRESS:
+ if (DBG) Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS");
+ synchronized(mConnection) {
+ if ((mBluetooth == null) && (!mBinding)) {
+ if (DBG) Slog.d(TAG, "Binding to service to get name and address");
+ mGetNameAddressOnly = true;
+ Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+ mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS);
+ Intent i = new Intent(IBluetooth.class.getName());
+ if (!doBind(i, mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+ UserHandle.CURRENT)) {
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+ } else {
+ mBinding = true;
+ }
+ } else if (mBluetooth != null) {
+ try {
+ storeNameAndAddress(mBluetooth.getName(),
+ mBluetooth.getAddress());
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to grab names", re);
+ }
+ if (mGetNameAddressOnly && !mEnable) {
+ unbindAndFinish();
+ }
+ mGetNameAddressOnly = false;
+ }
+ }
+ break;
+
case MESSAGE_ENABLE:
if (DBG) {
Slog.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
@@ -1177,6 +1210,12 @@
mBluetoothBinder = service;
mBluetooth = IBluetooth.Stub.asInterface(service);
+ if (!isNameAndAddressSet()) {
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
+ if (mGetNameAddressOnly) return;
+ }
+
try {
boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,
Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);
@@ -1187,15 +1226,6 @@
Slog.e(TAG,"Unable to call configHciSnoopLog", e);
}
- if (!isNameAndAddressSet()) {
- try {
- storeNameAndAddress(mBluetooth.getName(),
- mBluetooth.getAddress());
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to grab names", re);
- }
- }
-
//Register callback object
try {
mBluetooth.registerCallback(mBluetoothCallback);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index b8cbf16..811e34e 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -125,6 +125,7 @@
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
+import android.widget.Toast;
import java.io.File;
import java.io.FileDescriptor;
@@ -452,6 +453,7 @@
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
private View mSwitchingDialogTitleView;
+ private Toast mSubtypeSwitchedByShortCutToast;
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
private LocaleList mLastSystemLocales;
@@ -2952,6 +2954,27 @@
return;
}
setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
+ if (mSubtypeSwitchedByShortCutToast != null) {
+ mSubtypeSwitchedByShortCutToast.cancel();
+ mSubtypeSwitchedByShortCutToast = null;
+ }
+ if ((mImeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
+ // IME window is shown. The user should be able to visually understand that the
+ // subtype is changed in most of cases. To avoid UI overlap, we do not show a toast
+ // in this case.
+ return;
+ }
+ final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
+ if (newInputMethodInfo == null) {
+ return;
+ }
+ final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
+ newInputMethodInfo, mCurrentSubtype);
+ if (!TextUtils.isEmpty(toastText)) {
+ mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText.toString(),
+ Toast.LENGTH_SHORT);
+ mSubtypeSwitchedByShortCutToast.show();
+ }
}
}
@@ -3823,6 +3846,22 @@
}
}
+ private static String imeWindowStatusToString(final int imeWindowVis) {
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
+ sb.append("Active");
+ first = false;
+ }
+ if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
+ if (!first) {
+ sb.append("|");
+ }
+ sb.append("Visible");
+ }
+ return sb.toString();
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -3870,6 +3909,7 @@
method = mCurMethod;
p.println(" mCurMethod=" + mCurMethod);
p.println(" mEnabledSession=" + mEnabledSession);
+ p.println(" mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
p.println(" mShowRequested=" + mShowRequested
+ " mShowExplicitlyRequested=" + mShowExplicitlyRequested
+ " mShowForced=" + mShowForced
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index bb32303..8a0a62a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -782,7 +782,12 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- return readUserDataInternal(accounts, account, key);
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return null;
+ }
+ return readUserDataInternalLocked(accounts, account, key);
+ }
} finally {
restoreCallingIdentity(identityToken);
}
@@ -1869,44 +1874,57 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- setUserdataInternal(accounts, account, key, value);
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return;
+ }
+ setUserdataInternalLocked(accounts, account, key, value);
+ }
} finally {
restoreCallingIdentity(identityToken);
}
}
- private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+ private boolean accountExistsCacheLocked(UserAccounts accounts, Account account) {
+ if (accounts.accountCache.containsKey(account.type)) {
+ for (Account acc : accounts.accountCache.get(account.type)) {
+ if (acc.name.equals(account.name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void setUserdataInternalLocked(UserAccounts accounts, Account account, String key,
String value) {
if (account == null || key == null) {
return;
}
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId < 0) {
+ return;
+ }
+ long extrasId = getExtrasIdLocked(db, accountId, key);
+ if (extrasId < 0) {
+ extrasId = insertExtraLocked(db, accountId, key, value);
+ if (extrasId < 0) {
return;
}
- long extrasId = getExtrasIdLocked(db, accountId, key);
- if (extrasId < 0 ) {
- extrasId = insertExtraLocked(db, accountId, key, value);
- if (extrasId < 0) {
- return;
- }
- } else {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- if (1 != db.update(CE_TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
- return;
- }
-
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_VALUE, value);
+ if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+ return;
}
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
}
+ writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
}
}
@@ -2500,21 +2518,31 @@
userId);
return;
}
-
final int pid = Binder.getCallingPid();
final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
options.putInt(AccountManager.KEY_CALLER_UID, uid);
options.putInt(AccountManager.KEY_CALLER_PID, pid);
+ // Check to see if the Password should be included to the caller.
+ String callerPkg = optionsIn.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME);
+ boolean isPasswordForwardingAllowed = isPermitted(
+ callerPkg, uid, Manifest.permission.GET_PASSWORD_PRIVILEGED);
+
int usrId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(usrId);
logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD,
TABLE_ACCOUNTS, uid);
- new StartAccountSession(accounts, response, accountType, expectActivityLaunch,
- null /* accountName */, false /* authDetailsRequired */,
- true /* updateLastAuthenticationTime */) {
+ new StartAccountSession(
+ accounts,
+ response,
+ accountType,
+ expectActivityLaunch,
+ null /* accountName */,
+ false /* authDetailsRequired */,
+ true /* updateLastAuthenticationTime */,
+ isPasswordForwardingAllowed) {
@Override
public void run() throws RemoteException {
mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType,
@@ -2537,12 +2565,21 @@
/** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */
private abstract class StartAccountSession extends Session {
- public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response,
- String accountType, boolean expectActivityLaunch, String accountName,
- boolean authDetailsRequired, boolean updateLastAuthenticationTime) {
+ private final boolean mIsPasswordForwardingAllowed;
+
+ public StartAccountSession(
+ UserAccounts accounts,
+ IAccountManagerResponse response,
+ String accountType,
+ boolean expectActivityLaunch,
+ String accountName,
+ boolean authDetailsRequired,
+ boolean updateLastAuthenticationTime,
+ boolean isPasswordForwardingAllowed) {
super(accounts, response, accountType, expectActivityLaunch,
true /* stripAuthTokenFromResult */, accountName, authDetailsRequired,
updateLastAuthenticationTime);
+ mIsPasswordForwardingAllowed = isPasswordForwardingAllowed;
}
@Override
@@ -2555,6 +2592,10 @@
checkKeyIntent(
Binder.getCallingUid(),
intent);
+ // Omit passwords if the caller isn't permitted to see them.
+ if (!mIsPasswordForwardingAllowed) {
+ result.remove(AccountManager.KEY_PASSWORD);
+ }
}
IAccountManagerResponse response;
if (mExpectActivityLaunch && result != null
@@ -2901,6 +2942,12 @@
}
int userId = UserHandle.getCallingUserId();
+
+ // Check to see if the Password should be included to the caller.
+ String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME);
+ boolean isPasswordForwardingAllowed = isPermitted(
+ callerPkg, uid, Manifest.permission.GET_PASSWORD_PRIVILEGED);
+
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -2911,7 +2958,8 @@
expectActivityLaunch,
account.name,
false /* authDetailsRequired */,
- true /* updateLastCredentialTime */) {
+ true /* updateLastCredentialTime */,
+ isPasswordForwardingAllowed) {
@Override
public void run() throws RemoteException {
mAuthenticator.startUpdateCredentialsSession(this, account, authTokenType,
@@ -5262,17 +5310,16 @@
}
}
- protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- return userDataForAccount.get(key);
+ protected String readUserDataInternalLocked(
+ UserAccounts accounts, Account account, String key) {
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+ if (userDataForAccount == null) {
+ // need to populate the cache for this account
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+ accounts.userDataCache.put(account, userDataForAccount);
}
+ return userDataForAccount.get(key);
}
protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e82c6c7..1da6a20 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1105,20 +1105,20 @@
ComponentName mTopComponent;
String mTopAction = Intent.ACTION_MAIN;
String mTopData;
- boolean mProcessesReady = false;
- boolean mSystemReady = false;
- boolean mBooting = false;
- boolean mCallFinishBooting = false;
- boolean mBootAnimationComplete = false;
- boolean mOnBattery = false;
- boolean mLaunchWarningShown = false;
+
+ volatile boolean mProcessesReady = false;
+ volatile boolean mSystemReady = false;
+ volatile boolean mOnBattery = false;
+ volatile int mFactoryTest;
+
+ @GuardedBy("this") boolean mBooting = false;
+ @GuardedBy("this") boolean mCallFinishBooting = false;
+ @GuardedBy("this") boolean mBootAnimationComplete = false;
+ @GuardedBy("this") boolean mLaunchWarningShown = false;
+ @GuardedBy("this") boolean mCheckedForSetup = false;
Context mContext;
- int mFactoryTest;
-
- boolean mCheckedForSetup;
-
/**
* The time at which we will allow normal application switches again,
* after a call to {@link #stopAppSwitches()}.
@@ -9827,7 +9827,8 @@
public void updateLockTaskPackages(int userId, String[] packages) {
final int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- throw new SecurityException("updateLockTaskPackage called from non-system process");
+ enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
+ "updateLockTaskPackages()");
}
synchronized (this) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" +
@@ -18044,6 +18045,9 @@
ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
int changes = 0;
+ if (mWindowManager != null) {
+ mWindowManager.deferSurfaceLayout();
+ }
if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
@@ -18144,6 +18148,20 @@
null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
+ // Update the configuration with WM first and check if any of the stacks need to be
+ // resized due to the configuration change. If so, resize the stacks now and do any
+ // relaunches if necessary. This way we don't need to relaunch again below in
+ // ensureActivityConfigurationLocked().
+ if (mWindowManager != null) {
+ final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration);
+ if (resizedStacks != null) {
+ for (int stackId : resizedStacks) {
+ final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
+ mStackSupervisor.resizeStackLocked(
+ stackId, newBounds, null, null, false, false);
+ }
+ }
+ }
}
boolean kept = true;
@@ -18165,11 +18183,9 @@
!PRESERVE_WINDOWS);
}
}
-
- if (values != null && mWindowManager != null) {
- mWindowManager.setNewConfiguration(mConfiguration);
+ if (mWindowManager != null) {
+ mWindowManager.continueSurfaceLayout();
}
-
return kept;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d570be9..9be6b43 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -23,8 +23,6 @@
import android.os.UserHandle;
import android.util.DebugUtils;
-import com.android.internal.util.ArrayUtils;
-
import java.io.PrintWriter;
class ActivityManagerShellCommand extends ShellCommand {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0d70e99..811b48f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2738,12 +2738,21 @@
// Called when WindowManager has finished animating the launchingBehind activity to the back.
void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
- r.mLaunchTaskBehind = false;
final TaskRecord task = r.task;
- task.setLastThumbnailLocked(task.stack.screenshotActivitiesLocked(r));
+ final ActivityStack stack = task.stack;
+
+ r.mLaunchTaskBehind = false;
+ task.setLastThumbnailLocked(stack.screenshotActivitiesLocked(r));
mRecentTasks.addLocked(task);
mService.notifyTaskStackChangedLocked();
mWindowManager.setAppVisibility(r.appToken, false);
+
+ // When launching tasks behind, update the last active time of the top task after the new
+ // task has been shown briefly
+ final ActivityRecord top = stack.topActivity();
+ if (top != null) {
+ top.task.touchActiveTime();
+ }
}
void scheduleLaunchTaskBehindComplete(IBinder token) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index b157070..e32d1d1 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -285,6 +285,7 @@
mCallingPackage = info.packageName;
setIntent(_intent, info);
setMinDimensions(info);
+ touchActiveTime();
}
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
@@ -315,6 +316,7 @@
taskType = APPLICATION_ACTIVITY_TYPE;
mTaskToReturnTo = HOME_ACTIVITY_TYPE;
lastTaskDescription = _taskDescription;
+ touchActiveTime();
}
private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index a36e671..69c012e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -165,15 +165,13 @@
@ServiceThreadOnly
protected void onStandby(boolean initiatedByCec, int standbyAction) {
assertRunOnServiceThread();
- if (!mService.isControlEnabled() || initiatedByCec) {
+ if (!mService.isControlEnabled() || initiatedByCec || !mAutoTvOff) {
return;
}
switch (standbyAction) {
case HdmiControlService.STANDBY_SCREEN_OFF:
- if (mAutoTvOff) {
- mService.sendCecCommand(
- HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
- }
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
break;
case HdmiControlService.STANDBY_SHUTDOWN:
// ACTION_SHUTDOWN is taken as a signal to power off all the devices.
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index 0b201710..537df81 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -21,6 +21,7 @@
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;
+import android.util.Log;
import java.util.HashMap;
@@ -42,6 +43,7 @@
// Logging duration for same error message.
private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>();
@@ -83,10 +85,9 @@
}
private void debugInternal(String logMessage) {
- if (true || IS_USER_BUILD) {
- return;
+ if (DEBUG) {
+ Slog.d(TAG, logMessage);
}
- Slog.d(TAG, logMessage);
}
private static final String toLogString(String logMessage, Object[] objs) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 9cfb590..d339f69 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -24,7 +24,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -35,7 +34,6 @@
import android.net.NetworkTemplate;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
-import android.os.Binder;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.util.Log;
@@ -88,12 +86,10 @@
pw.println(" Adds a UID to the whitelist for restrict background usage.");
pw.println(" add restrict-background-blacklist UID");
pw.println(" Adds a UID to the blacklist for restrict background usage.");
- pw.println(" get metered-network ID");
- pw.println(" Checks whether the given non-mobile network is metered or not.");
pw.println(" get restrict-background");
pw.println(" Gets the global restrict background usage status.");
- pw.println(" list metered-networks [BOOLEAN]");
- pw.println(" Lists all non-mobile networks and whether they are metered or not.");
+ pw.println(" list wifi-networks [BOOLEAN]");
+ pw.println(" Lists all saved wifi networks and whether they are metered or not.");
pw.println(" If a boolean argument is passed, filters just the metered (or unmetered)");
pw.println(" networks.");
pw.println(" list restrict-background-whitelist");
@@ -105,7 +101,7 @@
pw.println(" remove restrict-background-blacklist UID");
pw.println(" Removes a UID from the blacklist for restrict background usage.");
pw.println(" set metered-network ID BOOLEAN");
- pw.println(" Toggles whether the given non-mobile network is metered.");
+ pw.println(" Toggles whether the given wi-fi network is metered.");
pw.println(" set restrict-background BOOLEAN");
pw.println(" Sets the global restrict background usage status.");
}
@@ -118,8 +114,6 @@
return -1;
}
switch(type) {
- case "metered-network":
- return getMeteredWifiNetwork();
case "restrict-background":
return getRestrictBackground();
}
@@ -152,8 +146,8 @@
return -1;
}
switch(type) {
- case "metered-networks":
- return listMeteredWifiNetworks();
+ case "wifi-networks":
+ return listWifiNetworks();
case "restrict-background-whitelist":
return listRestrictBackgroundWhitelist();
case "restrict-background-blacklist":
@@ -284,7 +278,7 @@
return 0;
}
- private int listMeteredWifiNetworks() throws RemoteException {
+ private int listWifiNetworks() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final String arg = getNextArg();
final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
@@ -299,23 +293,6 @@
return 0;
}
- private int getMeteredWifiNetwork() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- final String id = getNextArg();
- if (id == null) {
- pw.println("Error: didn't specify ID");
- return -1;
- }
- final List<NetworkPolicy> policies = getWifiPolicies();
- for (NetworkPolicy policy: policies) {
- if (id.equals(getNetworkId(policy))) {
- pw.println(policy.metered);
- return 0;
- }
- }
- return 0;
- }
-
private int setMeteredWifiNetwork() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final String id = getNextArg();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 022b10f..124d7f1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -120,6 +120,7 @@
import android.widget.Toast;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
@@ -226,7 +227,7 @@
private VrManagerInternal mVrManagerInternal;
final IBinder mForegroundToken = new Binder();
- private WorkerHandler mHandler;
+ private Handler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -572,33 +573,9 @@
public void clearEffects() {
synchronized (mNotificationList) {
if (DBG) Slog.d(TAG, "clearEffects");
-
- // sound
- mSoundNotificationKey = null;
-
- long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- player.stopAsync();
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- // vibrate
- mVibrateNotificationKey = null;
- identity = Binder.clearCallingIdentity();
- try {
- mVibrator.cancel();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- // light
- mLights.clear();
- updateLightsLocked();
+ clearSoundLocked();
+ clearVibrateLocked();
+ clearLightsLocked();
}
}
@@ -658,6 +635,36 @@
}
};
+ private void clearSoundLocked() {
+ mSoundNotificationKey = null;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+ if (player != null) {
+ player.stopAsync();
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void clearVibrateLocked() {
+ mVibrateNotificationKey = null;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mVibrator.cancel();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void clearLightsLocked() {
+ // light
+ mLights.clear();
+ updateLightsLocked();
+ }
+
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -863,6 +870,26 @@
super(context);
}
+ @VisibleForTesting
+ void setAudioManager(AudioManager audioMananger) {
+ mAudioManager = audioMananger;
+ }
+
+ @VisibleForTesting
+ void setVibrator(Vibrator vibrator) {
+ mVibrator = vibrator;
+ }
+
+ @VisibleForTesting
+ void setSystemReady(boolean systemReady) {
+ mSystemReady = systemReady;
+ }
+
+ @VisibleForTesting
+ void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
@Override
public void onStart() {
Resources resources = getContext().getResources();
@@ -2492,12 +2519,14 @@
return false;
}
- private void buzzBeepBlinkLocked(NotificationRecord record) {
+ @VisibleForTesting
+ void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false;
boolean beep = false;
boolean blink = false;
final Notification notification = record.sbn.getNotification();
+ final String key = record.getKey();
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;
@@ -2521,9 +2550,15 @@
if (disableEffects != null) {
ZenLog.traceDisableEffects(record, disableEffects);
}
+
+ // Remember if this notification already owns the notification channels.
+ boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
+ boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
+
+ // These are set inside the conditional if the notification is allowed to make noise.
+ boolean hasValidVibrate = false;
+ boolean hasValidSound = false;
if (disableEffects == null
- && (!(record.isUpdate
- && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (record.getUserId() == UserHandle.USER_ALL ||
record.getUserId() == currentUser ||
mUserProfiles.isCurrentProfile(record.getUserId()))
@@ -2532,10 +2567,6 @@
&& mAudioManager != null) {
if (DBG) Slog.v(TAG, "Interrupting!");
- sendAccessibilityEvent(notification, record.sbn.getPackageName());
-
- // sound
-
// should we use the default notification sound? (indicated either by
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
@@ -2545,8 +2576,6 @@
.equals(notification.sound);
Uri soundUri = null;
- boolean hasValidSound = false;
-
if (useDefaultSound) {
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
@@ -2559,88 +2588,105 @@
hasValidSound = (soundUri != null);
}
- if (hasValidSound) {
- boolean looping =
- (notification.flags & Notification.FLAG_INSISTENT) != 0;
- AudioAttributes audioAttributes = audioAttributesForNotification(notification);
- mSoundNotificationKey = record.getKey();
- // do not play notifications if stream volume is 0 (typically because
- // ringer mode is silent) or if there is a user of exclusive audio focus
- if ((mAudioManager.getStreamVolume(
- AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
- && !mAudioManager.isAudioFocusExclusive()) {
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player =
- mAudioManager.getRingtonePlayer();
- if (player != null) {
- if (DBG) Slog.v(TAG, "Playing sound " + soundUri
- + " with attributes " + audioAttributes);
- player.playAsync(soundUri, record.sbn.getUser(), looping,
- audioAttributes);
- beep = true;
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- // vibrate
// Does the notification want to specify its own vibration?
final boolean hasCustomVibrate = notification.vibrate != null;
// new in 4.2: if there was supposed to be a sound and we're in vibrate
// mode, and no other vibration is specified, we fall back to vibration
final boolean convertSoundToVibration =
- !hasCustomVibrate
- && hasValidSound
- && (mAudioManager.getRingerModeInternal()
- == AudioManager.RINGER_MODE_VIBRATE);
+ !hasCustomVibrate
+ && hasValidSound
+ && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);
// The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
- if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
- && !(mAudioManager.getRingerModeInternal()
- == AudioManager.RINGER_MODE_SILENT)) {
- mVibrateNotificationKey = record.getKey();
+ hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||
+ hasCustomVibrate;
- if (useDefaultVibrate || convertSoundToVibration) {
- // Escalate privileges so we can use the vibrator even if the
- // notifying app does not have the VIBRATE permission.
- long identity = Binder.clearCallingIdentity();
- try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
- useDefaultVibrate ? mDefaultVibrationPattern
- : mFallbackVibrationPattern,
- ((notification.flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, audioAttributesForNotification(notification));
- buzz = true;
- } finally {
- Binder.restoreCallingIdentity(identity);
+ // We can alert, and we're allowed to alert, but if the developer asked us to only do
+ // it once, and we already have, then don't.
+ if (!(record.isUpdate
+ && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) {
+
+ sendAccessibilityEvent(notification, record.sbn.getPackageName());
+
+ if (hasValidSound) {
+ boolean looping =
+ (notification.flags & Notification.FLAG_INSISTENT) != 0;
+ AudioAttributes audioAttributes = audioAttributesForNotification(notification);
+ mSoundNotificationKey = key;
+ // do not play notifications if stream volume is 0 (typically because
+ // ringer mode is silent) or if there is a user of exclusive audio focus
+ if ((mAudioManager.getStreamVolume(
+ AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
+ && !mAudioManager.isAudioFocusExclusive()) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final IRingtonePlayer player =
+ mAudioManager.getRingtonePlayer();
+ if (player != null) {
+ if (DBG) Slog.v(TAG, "Playing sound " + soundUri
+ + " with attributes " + audioAttributes);
+ player.playAsync(soundUri, record.sbn.getUser(), looping,
+ audioAttributes);
+ beep = true;
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
- } else if (notification.vibrate.length > 1) {
- // If you want your own vibration pattern, you need the VIBRATE
- // permission
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
- notification.vibrate,
- ((notification.flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, audioAttributesForNotification(notification));
- buzz = true;
+ }
+
+ if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()
+ == AudioManager.RINGER_MODE_SILENT)) {
+ mVibrateNotificationKey = key;
+
+ if (useDefaultVibrate || convertSoundToVibration) {
+ // Escalate privileges so we can use the vibrator even if the
+ // notifying app does not have the VIBRATE permission.
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ useDefaultVibrate ? mDefaultVibrationPattern
+ : mFallbackVibrationPattern,
+ ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+ ? 0: -1, audioAttributesForNotification(notification));
+ buzz = true;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else if (notification.vibrate.length > 1) {
+ // If you want your own vibration pattern, you need the VIBRATE
+ // permission
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ notification.vibrate,
+ ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+ ? 0: -1, audioAttributesForNotification(notification));
+ buzz = true;
+ }
}
}
+
+ }
+ // If a notification is updated to remove the actively playing sound or vibrate,
+ // cancel that feedback now
+ if (wasBeep && !hasValidSound) {
+ clearSoundLocked();
+ }
+ if (wasBuzz && !hasValidVibrate) {
+ clearVibrateLocked();
}
// light
// release the light
- boolean wasShowLights = mLights.remove(record.getKey());
+ boolean wasShowLights = mLights.remove(key);
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
&& ((record.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
- mLights.add(record.getKey());
+ mLights.add(key);
updateLightsLocked();
if (mUseAttentionLight) {
mAttentionLight.pulse();
@@ -2654,7 +2700,7 @@
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
} else {
- EventLogTags.writeNotificationAlert(record.getKey(),
+ EventLogTags.writeNotificationAlert(key,
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
mHandler.post(mBuzzBeepBlinked);
}
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 13a96ae..098b39e 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -247,10 +247,8 @@
}
// SetupWizard
- Intent setupIntent = new Intent(Intent.ACTION_MAIN);
- setupIntent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
- PackageParser.Package setupPackage = getDefaultSystemHandlerActivityPackageLPr(
- setupIntent, userId);
+ PackageParser.Package setupPackage = getSystemPackageLPr(
+ mService.mSetupWizardPackage);
if (setupPackage != null
&& doesPackageSupportRuntimePermissions(setupPackage)) {
grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
@@ -597,6 +595,16 @@
grantRuntimePermissionsLPw(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
}
+ // NFC Tag viewer
+ Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
+ nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
+ PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackageLPr(
+ nfcTagIntent, userId);
+ if (nfcTagPkg != null
+ && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
+ grantRuntimePermissionsLPw(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
+ grantRuntimePermissionsLPw(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
+ }
mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7c71fbc..4899e34 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -60,6 +60,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -121,6 +122,21 @@
return getCallingUid();
}
+ final int injectCallingUserId() {
+ return UserHandle.getUserId(injectBinderCallingUid());
+ }
+
+ @VisibleForTesting
+ long injectClearCallingIdentity() {
+ return Binder.clearCallingIdentity();
+ }
+
+ // Injection point.
+ @VisibleForTesting
+ void injectRestoreCallingIdentity(long token) {
+ Binder.restoreCallingIdentity(token);
+ }
+
private int getCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
@@ -197,14 +213,13 @@
/**
* Checks if the caller is in the same group as the userToCheck.
*/
- @VisibleForTesting // We override it in unit tests
- void ensureInUserProfiles(UserHandle userToCheck, String message) {
- final int callingUserId = UserHandle.getCallingUserId();
+ private void ensureInUserProfiles(UserHandle userToCheck, String message) {
+ final int callingUserId = injectCallingUserId();
final int targetUserId = userToCheck.getIdentifier();
if (targetUserId == callingUserId) return;
- long ident = Binder.clearCallingIdentity();
+ long ident = injectClearCallingIdentity();
try {
UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
@@ -214,7 +229,7 @@
throw new SecurityException(message);
}
} finally {
- Binder.restoreCallingIdentity(ident);
+ injectRestoreCallingIdentity(ident);
}
}
@@ -239,12 +254,12 @@
* Checks if the user is enabled.
*/
private boolean isUserEnabled(UserHandle user) {
- long ident = Binder.clearCallingIdentity();
+ long ident = injectClearCallingIdentity();
try {
UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier());
return targetUserInfo != null && targetUserInfo.isEnabled();
} finally {
- Binder.restoreCallingIdentity(ident);
+ injectRestoreCallingIdentity(ident);
}
}
@@ -345,6 +360,9 @@
public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
String packageName, ComponentName componentName, int flags, UserHandle user) {
ensureShortcutPermission(callingPackage, user);
+ if (!isUserEnabled(user)) {
+ return new ParceledListSlice<>(new ArrayList(0));
+ }
return new ParceledListSlice<>(
mShortcutServiceInternal.getShortcuts(getCallingUserId(),
@@ -356,6 +374,9 @@
public ParceledListSlice getShortcutInfo(String callingPackage, String packageName,
List<String> ids, UserHandle user) {
ensureShortcutPermission(callingPackage, user);
+ if (!isUserEnabled(user)) {
+ return new ParceledListSlice<>(new ArrayList(0));
+ }
return new ParceledListSlice<>(
mShortcutServiceInternal.getShortcutInfo(getCallingUserId(),
@@ -366,6 +387,10 @@
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle user) {
ensureShortcutPermission(callingPackage, user);
+ if (!isUserEnabled(user)) {
+ throw new IllegalStateException("Cannot pin shortcuts for disabled profile "
+ + user);
+ }
mShortcutServiceInternal.pinShortcuts(getCallingUserId(),
callingPackage, packageName, ids, user.getIdentifier());
@@ -375,6 +400,9 @@
public int getShortcutIconResId(String callingPackage, ShortcutInfo shortcut,
UserHandle user) {
ensureShortcutPermission(callingPackage, user);
+ if (!isUserEnabled(user)) {
+ return 0;
+ }
return mShortcutServiceInternal.getShortcutIconResId(getCallingUserId(),
callingPackage, shortcut, user.getIdentifier());
@@ -384,6 +412,9 @@
public ParcelFileDescriptor getShortcutIconFd(String callingPackage, ShortcutInfo shortcut,
UserHandle user) {
ensureShortcutPermission(callingPackage, user);
+ if (!isUserEnabled(user)) {
+ return null;
+ }
return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(),
callingPackage, shortcut, user.getIdentifier());
@@ -402,6 +433,11 @@
verifyCallingPackage(callingPackage);
ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ throw new IllegalStateException("Cannot start a shortcut for disabled profile "
+ + user);
+ }
+
// Even without the permission, pinned shortcuts are always launchable.
if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
callingPackage, packageName, shortcutId, user.getIdentifier())) {
@@ -530,13 +566,13 @@
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
- boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
+ private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
String debugMsg) {
if (user.getIdentifier() == listeningUser.getIdentifier()) {
if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg);
return true;
}
- long ident = Binder.clearCallingIdentity();
+ long ident = injectClearCallingIdentity();
try {
UserInfo userInfo = mUm.getUserInfo(user.getIdentifier());
UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier());
@@ -557,7 +593,7 @@
return true;
}
} finally {
- Binder.restoreCallingIdentity(ident);
+ injectRestoreCallingIdentity(ident);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c2fd8fe..4ce730f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -76,6 +76,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
import static android.content.pm.PackageParser.isApkFile;
+import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -115,6 +116,7 @@
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentFilter.AuthorityEntry;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
@@ -147,6 +149,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.IntentInfo;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageStats;
@@ -320,6 +323,7 @@
private static final boolean DEBUG_INTENT_MATCHING = false;
private static final boolean DEBUG_PACKAGE_SCANNING = false;
private static final boolean DEBUG_VERIFY = false;
+ private static final boolean DEBUG_FILTERS = false;
// Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
// and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -445,6 +449,18 @@
sBrowserIntent.setData(Uri.parse("http:"));
}
+ /**
+ * The set of all protected actions [i.e. those actions for which a high priority
+ * intent filter is disallowed].
+ */
+ private static final Set<String> PROTECTED_ACTIONS = new ArraySet<>();
+ static {
+ PROTECTED_ACTIONS.add(Intent.ACTION_SEND);
+ PROTECTED_ACTIONS.add(Intent.ACTION_SENDTO);
+ PROTECTED_ACTIONS.add(Intent.ACTION_SEND_MULTIPLE);
+ PROTECTED_ACTIONS.add(Intent.ACTION_VIEW);
+ }
+
// Compilation reasons.
public static final int REASON_FIRST_BOOT = 0;
public static final int REASON_BOOT = 1;
@@ -531,6 +547,20 @@
* are package location.
*/
final private ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
+ /**
+ * Tracks high priority intent filters for protected actions. During boot, certain
+ * filter actions are protected and should never be allowed to have a high priority
+ * intent filter for them. However, there is one, and only one exception -- the
+ * setup wizard. It must be able to define a high priority intent filter for these
+ * actions to ensure there are no escapes from the wizard. We need to delay processing
+ * of these during boot as we need to look at all of the system packages in order
+ * to know which component is the setup wizard.
+ */
+ private final List<PackageParser.ActivityIntentInfo> mProtectedFilters = new ArrayList<>();
+ /**
+ * Whether or not processing protected filters should be deferred.
+ */
+ private boolean mDeferProtectedFilters = true;
/**
* Tracks existing system packages prior to receiving an OTA. Keys are package name.
@@ -1035,6 +1065,7 @@
final @Nullable String mRequiredVerifierPackage;
final @Nullable String mRequiredInstallerPackage;
+ final @Nullable String mSetupWizardPackage;
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -2428,6 +2459,36 @@
}
mExpectingBetter.clear();
+ // Resolve protected action filters. Only the setup wizard is allowed to
+ // have a high priority filter for these actions.
+ mSetupWizardPackage = getSetupWizardPackageName();
+ if (mProtectedFilters.size() > 0) {
+ if (DEBUG_FILTERS && mSetupWizardPackage == null) {
+ Slog.i(TAG, "No setup wizard;"
+ + " All protected intents capped to priority 0");
+ }
+ for (ActivityIntentInfo filter : mProtectedFilters) {
+ if (filter.activity.info.packageName.equals(mSetupWizardPackage)) {
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Found setup wizard;"
+ + " allow priority " + filter.getPriority() + ";"
+ + " package: " + filter.activity.info.packageName
+ + " activity: " + filter.activity.className
+ + " priority: " + filter.getPriority());
+ }
+ // skip setup wizard; allow it to keep the high priority filter
+ continue;
+ }
+ Slog.w(TAG, "Protected action; cap priority to 0;"
+ + " package: " + filter.activity.info.packageName
+ + " activity: " + filter.activity.className
+ + " origPrio: " + filter.getPriority());
+ filter.setPriority(0);
+ }
+ }
+ mDeferProtectedFilters = false;
+ mProtectedFilters.clear();
+
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
updateAllSharedLibrariesLPw();
@@ -9697,6 +9758,12 @@
// is granted only if it was already granted.
allowed = origPermissions.hasInstallPermission(perm);
}
+ if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0
+ && pkg.packageName.equals(mSetupWizardPackage)) {
+ // If this permission is to be granted to the system setup wizard and
+ // this app is a setup wizard, then it gets the permission.
+ allowed = true;
+ }
}
return allowed;
}
@@ -9754,8 +9821,314 @@
return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}
+ /**
+ * Finds a privileged activity that matches the specified activity names.
+ */
+ private PackageParser.Activity findMatchingActivity(
+ List<PackageParser.Activity> activityList, ActivityInfo activityInfo) {
+ for (PackageParser.Activity sysActivity : activityList) {
+ if (sysActivity.info.name.equals(activityInfo.name)) {
+ return sysActivity;
+ }
+ if (sysActivity.info.name.equals(activityInfo.targetActivity)) {
+ return sysActivity;
+ }
+ if (sysActivity.info.targetActivity != null) {
+ if (sysActivity.info.targetActivity.equals(activityInfo.name)) {
+ return sysActivity;
+ }
+ if (sysActivity.info.targetActivity.equals(activityInfo.targetActivity)) {
+ return sysActivity;
+ }
+ }
+ }
+ return null;
+ }
+
+ public class IterGenerator<E> {
+ public Iterator<E> generate(ActivityIntentInfo info) {
+ return null;
+ }
+ }
+
+ public class ActionIterGenerator extends IterGenerator<String> {
+ @Override
+ public Iterator<String> generate(ActivityIntentInfo info) {
+ return info.actionsIterator();
+ }
+ }
+
+ public class CategoriesIterGenerator extends IterGenerator<String> {
+ @Override
+ public Iterator<String> generate(ActivityIntentInfo info) {
+ return info.categoriesIterator();
+ }
+ }
+
+ public class SchemesIterGenerator extends IterGenerator<String> {
+ @Override
+ public Iterator<String> generate(ActivityIntentInfo info) {
+ return info.schemesIterator();
+ }
+ }
+
+ public class AuthoritiesIterGenerator extends IterGenerator<IntentFilter.AuthorityEntry> {
+ @Override
+ public Iterator<IntentFilter.AuthorityEntry> generate(ActivityIntentInfo info) {
+ return info.authoritiesIterator();
+ }
+ }
+
+ /**
+ * <em>WARNING</em> for performance reasons, the passed in intentList WILL BE
+ * MODIFIED. Do not pass in a list that should not be changed.
+ */
+ private <T> void getIntentListSubset(List<ActivityIntentInfo> intentList,
+ IterGenerator<T> generator, Iterator<T> searchIterator) {
+ // loop through the set of actions; every one must be found in the intent filter
+ while (searchIterator.hasNext()) {
+ // we must have at least one filter in the list to consider a match
+ if (intentList.size() == 0) {
+ break;
+ }
+
+ final T searchAction = searchIterator.next();
+
+ // loop through the set of intent filters
+ final Iterator<ActivityIntentInfo> intentIter = intentList.iterator();
+ while (intentIter.hasNext()) {
+ final ActivityIntentInfo intentInfo = intentIter.next();
+ boolean selectionFound = false;
+
+ // loop through the intent filter's selection criteria; at least one
+ // of them must match the searched criteria
+ final Iterator<T> intentSelectionIter = generator.generate(intentInfo);
+ while (intentSelectionIter != null && intentSelectionIter.hasNext()) {
+ final T intentSelection = intentSelectionIter.next();
+ if (intentSelection != null && intentSelection.equals(searchAction)) {
+ selectionFound = true;
+ break;
+ }
+ }
+
+ // the selection criteria wasn't found in this filter's set; this filter
+ // is not a potential match
+ if (!selectionFound) {
+ intentIter.remove();
+ }
+ }
+ }
+ }
+
+ private boolean isProtectedAction(ActivityIntentInfo filter) {
+ final Iterator<String> actionsIter = filter.actionsIterator();
+ while (actionsIter != null && actionsIter.hasNext()) {
+ final String filterAction = actionsIter.next();
+ if (PROTECTED_ACTIONS.contains(filterAction)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adjusts the priority of the given intent filter according to policy.
+ * <p>
+ * <ul>
+ * <li>The priority for non privileged applications is capped to '0'</li>
+ * <li>The priority for protected actions on privileged applications is capped to '0'</li>
+ * <li>The priority for unbundled updates to privileged applications is capped to the
+ * priority defined on the system partition</li>
+ * </ul>
+ * <p>
+ * <em>NOTE:</em> There is one exception. For security reasons, the setup wizard is
+ * allowed to obtain any priority on any action.
+ */
+ private void adjustPriority(
+ List<PackageParser.Activity> systemActivities, ActivityIntentInfo intent) {
+ // nothing to do; priority is fine as-is
+ if (intent.getPriority() <= 0) {
+ return;
+ }
+
+ final ActivityInfo activityInfo = intent.activity.info;
+ final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
+
+ final boolean privilegedApp =
+ ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0);
+ if (!privilegedApp) {
+ // non-privileged applications can never define a priority >0
+ Slog.w(TAG, "Non-privileged app; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ intent.setPriority(0);
+ return;
+ }
+
+ if (systemActivities == null) {
+ // the system package is not disabled; we're parsing the system partition
+ if (isProtectedAction(intent)) {
+ if (mDeferProtectedFilters) {
+ // We can't deal with these just yet. No component should ever obtain a
+ // >0 priority for a protected actions, with ONE exception -- the setup
+ // wizard. The setup wizard, however, cannot be known until we're able to
+ // query it for the category CATEGORY_SETUP_WIZARD. Which we can't do
+ // until all intent filters have been processed. Chicken, meet egg.
+ // Let the filter temporarily have a high priority and rectify the
+ // priorities after all system packages have been scanned.
+ mProtectedFilters.add(intent);
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Protected action; save for later;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ return;
+ } else {
+ if (DEBUG_FILTERS && mSetupWizardPackage == null) {
+ Slog.i(TAG, "No setup wizard;"
+ + " All protected intents capped to priority 0");
+ }
+ if (intent.activity.info.packageName.equals(mSetupWizardPackage)) {
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Found setup wizard;"
+ + " allow priority " + intent.getPriority() + ";"
+ + " package: " + intent.activity.info.packageName
+ + " activity: " + intent.activity.className
+ + " priority: " + intent.getPriority());
+ }
+ // setup wizard gets whatever it wants
+ return;
+ }
+ Slog.w(TAG, "Protected action; cap priority to 0;"
+ + " package: " + intent.activity.info.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ intent.setPriority(0);
+ return;
+ }
+ }
+ // privileged apps on the system image get whatever priority they request
+ return;
+ }
+
+ // privileged app unbundled update ... try to find the same activity
+ final PackageParser.Activity foundActivity =
+ findMatchingActivity(systemActivities, activityInfo);
+ if (foundActivity == null) {
+ // this is a new activity; it cannot obtain >0 priority
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "New activity; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(0);
+ return;
+ }
+
+ // found activity, now check for filter equivalence
+
+ // a shallow copy is enough; we modify the list, not its contents
+ final List<ActivityIntentInfo> intentListCopy =
+ new ArrayList<>(foundActivity.intents);
+ final List<ActivityIntentInfo> foundFilters = findFilters(intent);
+
+ // find matching action subsets
+ final Iterator<String> actionsIterator = intent.actionsIterator();
+ if (actionsIterator != null) {
+ getIntentListSubset(
+ intentListCopy, new ActionIterGenerator(), actionsIterator);
+ if (intentListCopy.size() == 0) {
+ // no more intents to match; we're not equivalent
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Mismatched action; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(0);
+ return;
+ }
+ }
+
+ // find matching category subsets
+ final Iterator<String> categoriesIterator = intent.categoriesIterator();
+ if (categoriesIterator != null) {
+ getIntentListSubset(intentListCopy, new CategoriesIterGenerator(),
+ categoriesIterator);
+ if (intentListCopy.size() == 0) {
+ // no more intents to match; we're not equivalent
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Mismatched category; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(0);
+ return;
+ }
+ }
+
+ // find matching schemes subsets
+ final Iterator<String> schemesIterator = intent.schemesIterator();
+ if (schemesIterator != null) {
+ getIntentListSubset(intentListCopy, new SchemesIterGenerator(),
+ schemesIterator);
+ if (intentListCopy.size() == 0) {
+ // no more intents to match; we're not equivalent
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Mismatched scheme; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(0);
+ return;
+ }
+ }
+
+ // find matching authorities subsets
+ final Iterator<IntentFilter.AuthorityEntry>
+ authoritiesIterator = intent.authoritiesIterator();
+ if (authoritiesIterator != null) {
+ getIntentListSubset(intentListCopy,
+ new AuthoritiesIterGenerator(),
+ authoritiesIterator);
+ if (intentListCopy.size() == 0) {
+ // no more intents to match; we're not equivalent
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Mismatched authority; cap priority to 0;"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(0);
+ return;
+ }
+ }
+
+ // we found matching filter(s); app gets the max priority of all intents
+ int cappedPriority = 0;
+ for (int i = intentListCopy.size() - 1; i >= 0; --i) {
+ cappedPriority = Math.max(cappedPriority, intentListCopy.get(i).getPriority());
+ }
+ if (intent.getPriority() > cappedPriority) {
+ if (DEBUG_FILTERS) {
+ Slog.i(TAG, "Found matching filter(s);"
+ + " cap priority to " + cappedPriority + ";"
+ + " package: " + applicationInfo.packageName
+ + " activity: " + intent.activity.className
+ + " origPrio: " + intent.getPriority());
+ }
+ intent.setPriority(cappedPriority);
+ return;
+ }
+ // all this for nothing; the requested priority was <= what was on the system
+ }
+
public final void addActivity(PackageParser.Activity a, String type) {
- final boolean systemApp = a.info.applicationInfo.isSystemApp();
mActivities.put(a.getComponentName(), a);
if (DEBUG_SHOW_INFO)
Log.v(
@@ -9766,10 +10139,12 @@
final int NI = a.intents.size();
for (int j=0; j<NI; j++) {
PackageParser.ActivityIntentInfo intent = a.intents.get(j);
- if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {
- intent.setPriority(0);
- Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity "
- + a.className + " with priority > 0, forcing to 0");
+ if ("activity".equals(type)) {
+ final PackageSetting ps =
+ mSettings.getDisabledSystemPkgLPr(intent.activity.info.packageName);
+ final List<PackageParser.Activity> systemActivities =
+ ps != null && ps.pkg != null ? ps.pkg.activities : null;
+ adjustPriority(systemActivities, intent);
}
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " IntentFilter:");
@@ -9919,18 +10294,6 @@
out.println();
}
-// List<ResolveInfo> filterEnabled(List<ResolveInfo> resolveInfoList) {
-// final Iterator<ResolveInfo> i = resolveInfoList.iterator();
-// final List<ResolveInfo> retList = Lists.newArrayList();
-// while (i.hasNext()) {
-// final ResolveInfo resolveInfo = i.next();
-// if (isEnabledLP(resolveInfo.activityInfo)) {
-// retList.add(resolveInfo);
-// }
-// }
-// return retList;
-// }
-
// Keys are String (activity class name), values are Activity.
private final ArrayMap<ComponentName, PackageParser.Activity> mActivities
= new ArrayMap<ComponentName, PackageParser.Activity>();
@@ -11282,8 +11645,8 @@
* @return the current "allow unknown sources" setting
*/
private int getUnknownSourcesSettings() {
- return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.INSTALL_NON_MARKET_APPS,
+ return android.provider.Settings.Secure.getInt(mContext.getContentResolver(),
+ android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS,
-1);
}
@@ -12247,8 +12610,6 @@
* Called after the source arguments are copied. This is used mostly for
* MoveParams when it needs to read the source file to put it in the
* destination.
- *
- * @return
*/
int doPostCopy(int uid) {
return PackageManager.INSTALL_SUCCEEDED;
@@ -15494,17 +15855,14 @@
// Otherwise, reset the permission.
final int revokeResult = permissionsState.revokeRuntimePermission(bp, userId);
switch (revokeResult) {
- case PERMISSION_OPERATION_SUCCESS: {
- writeRuntimePermissions = true;
- } break;
-
+ case PERMISSION_OPERATION_SUCCESS:
case PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
writeRuntimePermissions = true;
final int appId = ps.appId;
mHandler.post(new Runnable() {
@Override
public void run() {
- killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
+ killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
}
});
} break;
@@ -16619,6 +16977,21 @@
set, comp, userId);
}
+ private @Nullable String getSetupWizardPackageName() {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
+
+ final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+ MATCH_SYSTEM_ONLY | MATCH_DISABLED_COMPONENTS, UserHandle.myUserId());
+ if (matches.size() == 1) {
+ return matches.get(0).getComponentInfo().packageName;
+ } else {
+ Slog.e(TAG, "There should probably be exactly one setup wizard; found " + matches.size()
+ + ": matches=" + matches);
+ return null;
+ }
+ }
+
@Override
public void setApplicationEnabledSetting(String appPackageName,
int newState, int flags, int userId, String callingPackage) {
diff --git a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
deleted file mode 100644
index a0fbc37..0000000
--- a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.app.backup.BlobBackupHelper;
-
-public class ShortcutBackupAgent extends BlobBackupHelper {
- private static final String TAG = "ShortcutBackupAgent";
- private static final int BLOB_VERSION = 1;
-
- public ShortcutBackupAgent(int currentBlobVersion, String... keys) {
- super(currentBlobVersion, keys);
- }
-
- @Override
- protected byte[] getBackupPayload(String key) {
- throw new RuntimeException("not implemented yet"); // todo
- }
-
- @Override
- protected void applyRestoredPayload(String key, byte[] payload) {
- throw new RuntimeException("not implemented yet"); // todo
- }
-}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 7699f30..c6d66fe 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,10 @@
import android.content.pm.ShortcutInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +31,7 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -43,15 +48,16 @@
private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
private static final String ATTR_VALUE = "value";
private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_PACKAGE_USER_ID = "package-user";
private final int mOwnerUserId;
/**
* Package name -> IDs.
*/
- final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+ final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
- public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+ private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
mOwnerUserId = ownerUserId;
@@ -59,7 +65,7 @@
public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId) {
- this(launcherUserId, packageName, launcherUserId, null);
+ this(ownerUserId, packageName, launcherUserId, null);
}
@Override
@@ -67,16 +73,38 @@
return mOwnerUserId;
}
+ /**
+ * Called when the new package can't receive the backup, due to signature or version mismatch.
+ */
+ @Override
+ protected void onRestoreBlocked(ShortcutService s) {
+ final ArrayList<PackageWithUser> pinnedPackages =
+ new ArrayList<>(mPinnedShortcuts.keySet());
+ mPinnedShortcuts.clear();
+ for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
+ final PackageWithUser pu = pinnedPackages.get(i);
+ s.getPackageShortcutsLocked(pu.packageName, pu.userId)
+ .refreshPinnedFlags(s);
+ }
+ }
+
+ @Override
+ protected void onRestored(ShortcutService s) {
+ // Nothing to do.
+ }
+
public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids) {
final ShortcutPackage packageShortcuts =
s.getPackageShortcutsLocked(packageName, packageUserId);
+ final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+
final int idSize = ids.size();
if (idSize == 0) {
- mPinnedShortcuts.remove(packageName);
+ mPinnedShortcuts.remove(pu);
} else {
- final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
+ final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
// Pin shortcuts. Make sure only pin the ones that were visible to the caller.
// i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
@@ -93,7 +121,7 @@
newSet.add(id);
}
}
- mPinnedShortcuts.put(packageName, newSet);
+ mPinnedShortcuts.put(pu, newSet);
}
packageShortcuts.refreshPinnedFlags(s);
}
@@ -101,12 +129,13 @@
/**
* Return the pinned shortcut IDs for the publisher package.
*/
- public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
- return mPinnedShortcuts.get(packageName);
+ public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
+ @UserIdInt int packageUserId) {
+ return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
}
- boolean cleanUpPackage(String packageName) {
- return mPinnedShortcuts.remove(packageName) != null;
+ boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
+ return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
/**
@@ -126,9 +155,15 @@
getPackageInfo().saveToXml(out);
for (int i = 0; i < size; i++) {
+ final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
+ if (forBackup && (pu.userId != getOwnerUserId())) {
+ continue; // Target package on a different user, skip. (i.e. work profile)
+ }
+
out.startTag(null, TAG_PACKAGE);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
- mPinnedShortcuts.keyAt(i));
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -157,8 +192,6 @@
final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
launcherUserId);
- ShortcutPackageInfo spi = null;
-
ArraySet<String> ids = null;
final int outerDepth = parser.getDepth();
int type;
@@ -172,13 +205,17 @@
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
- spi = ShortcutPackageInfo.loadFromXml(parser);
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_PACKAGE: {
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_PACKAGE_NAME);
+ final int packageUserId = fromBackup ? ownerUserId
+ : ShortcutService.parseIntAttribute(parser,
+ ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
- ret.mPinnedShortcuts.put(packageName, ids);
+ ret.mPinnedShortcuts.put(
+ PackageWithUser.of(packageUserId, packageName), ids);
continue;
}
}
@@ -186,17 +223,17 @@
if (depth == outerDepth + 2) {
switch (tag) {
case TAG_PIN: {
- ids.add(ShortcutService.parseStringAttribute(parser,
- ATTR_VALUE));
+ if (ids == null) {
+ Slog.w(TAG, TAG_PIN + " in invalid place");
+ } else {
+ ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
+ }
continue;
}
}
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- if (spi != null) {
- ret.replacePackageInfo(spi);
- }
return ret;
}
@@ -208,6 +245,8 @@
pw.print(getPackageName());
pw.print(" Package user: ");
pw.print(getPackageUserId());
+ pw.print(" Owner user: ");
+ pw.print(getOwnerUserId());
pw.println();
getPackageInfo().dump(s, pw, prefix + " ");
@@ -217,10 +256,14 @@
for (int i = 0; i < size; i++) {
pw.println();
+ final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
pw.print(prefix);
pw.print(" ");
pw.print("Package: ");
- pw.println(mPinnedShortcuts.keyAt(i));
+ pw.print(pu.packageName);
+ pw.print(" User: ");
+ pw.println(pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -233,4 +276,9 @@
}
}
}
+
+ @VisibleForTesting
+ ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
+ return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 5916202..1076a7a 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -26,6 +26,8 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -34,6 +36,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
@@ -83,7 +86,7 @@
*/
private long mLastResetTime;
- public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
+ private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
}
@@ -97,6 +100,19 @@
return getPackageUserId();
}
+ @Override
+ protected void onRestoreBlocked(ShortcutService s) {
+ // Can't restore due to version/signature mismatch. Remove all shortcuts.
+ mShortcuts.clear();
+ }
+
+ @Override
+ protected void onRestored(ShortcutService s) {
+ // Because some launchers may not have been restored (e.g. allowBackup=false),
+ // we need to re-calculate the pinned shortcuts.
+ refreshPinnedFlags(s);
+ }
+
/**
* Note this does *not* provide a correct view to the calling launcher.
*/
@@ -229,20 +245,26 @@
s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
for (int l = launchers.size() - 1; l >= 0; l--) {
+ // Note even if a launcher that hasn't been installed can still pin shortcuts.
+
final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
- getPackageName());
+ getPackageName(), getPackageUserId());
if (pinned == null || pinned.size() == 0) {
continue;
}
for (int i = pinned.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
+ final String id = pinned.valueAt(i);
+ final ShortcutInfo si = mShortcuts.get(id);
if (si == null) {
- s.wtf("Shortcut not found");
- } else {
- si.addFlags(ShortcutInfo.FLAG_PINNED);
+ // This happens if a launcher pinned shortcuts from this package, then backup&
+ // restored, but this package doesn't allow backing up.
+ // In that case the launcher ends up having a dangling pinned shortcuts.
+ // That's fine, when the launcher is restored, we'll fix it.
+ continue;
}
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
}
}
@@ -312,11 +334,15 @@
public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId) {
+ if (getPackageInfo().isShadow()) {
+ // Restored and the app not installed yet, so don't return any.
+ return;
+ }
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
- : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
- .getPinnedShortcutIds(getPackageName());
+ : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
+ .getPinnedShortcutIds(getPackageName(), getPackageUserId());
for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -328,7 +354,8 @@
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
if (!si.isDynamic()) {
if (!si.isPinned()) {
- s.wtf("Shortcut not pinned here");
+ s.wtf("Shortcut not pinned: package " + getPackageName()
+ + ", user=" + getPackageUserId() + ", id=" + si.getId());
continue;
}
if (!isPinnedByCaller) {
@@ -479,7 +506,6 @@
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
- ShortcutPackageInfo spi = null;
final int outerDepth = parser.getDepth();
int type;
@@ -493,7 +519,7 @@
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
- spi = ShortcutPackageInfo.loadFromXml(parser);
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName);
@@ -505,9 +531,6 @@
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- if (spi != null) {
- ret.replacePackageInfo(spi);
- }
return ret;
}
@@ -567,4 +590,9 @@
intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
iconRes, bitmapPath);
}
+
+ @VisibleForTesting
+ List<ShortcutInfo> getAllShortcutsForTest() {
+ return new ArrayList<>(mShortcuts.values());
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 5f706b8..2c45890 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -67,10 +67,6 @@
return mIsShadow;
}
- public boolean isInstalled() {
- return !mIsShadow;
- }
-
public void setShadow(boolean shadow) {
mIsShadow = shadow;
}
@@ -79,14 +75,24 @@
return mVersionCode;
}
- public boolean canRestoreTo(PackageInfo target) {
+ public boolean hasSignatures() {
+ return mSigHashes.size() > 0;
+ }
+
+ public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
+ if (!s.shouldBackupApp(target)) {
+ // "allowBackup" was true when backed up, but now false.
+ Slog.w(TAG, "Can't restore: package no longer allows backup");
+ return false;
+ }
if (target.versionCode < mVersionCode) {
- Slog.w(TAG, String.format("Package current version %d < backed up version %d",
+ Slog.w(TAG, String.format(
+ "Can't restore: package current version %d < backed up version %d",
target.versionCode, mVersionCode));
return false;
}
if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
- Slog.w(TAG, "Package signature mismtach");
+ Slog.w(TAG, "Can't restore: Package signature mismatch");
return false;
}
return true;
@@ -106,6 +112,11 @@
}
public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+ if (mIsShadow) {
+ s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
+ + ", user=" + pkg.getOwnerUserId());
+ return;
+ }
// Note use mUserId here, rather than userId.
final PackageInfo pi = s.getPackageInfoWithSignatures(
pkg.getPackageName(), pkg.getPackageUserId());
@@ -132,15 +143,17 @@
out.endTag(null, TAG_ROOT);
}
- public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
+ public void loadFromXml(XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
- final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
+
+ // When restoring from backup, it's always shadow.
+ final boolean shadow =
+ fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
final ArrayList<byte[]> hashes = new ArrayList<>();
-
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -163,7 +176,11 @@
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- return new ShortcutPackageInfo(versionCode, hashes, shadow);
+
+ // Successfully loaded; replace the feilds.
+ mVersionCode = versionCode;
+ mIsShadow = shadow;
+ mSigHashes = hashes;
}
public void dump(ShortcutService s, PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index de2709d..f31dd17 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
import com.android.internal.util.Preconditions;
@@ -25,10 +27,12 @@
import java.io.IOException;
abstract class ShortcutPackageItem {
+ private static final String TAG = ShortcutService.TAG;
+
private final int mPackageUserId;
private final String mPackageName;
- private ShortcutPackageInfo mPackageInfo;
+ private final ShortcutPackageInfo mPackageInfo;
protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
@NonNull ShortcutPackageInfo packageInfo) {
@@ -61,25 +65,62 @@
return mPackageInfo;
}
- /**
- * Should be only used when loading from a file.o
- */
- protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
- mPackageInfo = Preconditions.checkNotNull(packageInfo);
- }
-
public void refreshPackageInfoAndSave(ShortcutService s) {
+ if (mPackageInfo.isShadow()) {
+ return; // Don't refresh for shadow user.
+ }
mPackageInfo.refresh(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
- public void ensureNotShadowAndSave(ShortcutService s) {
- if (mPackageInfo.isShadow()) {
- mPackageInfo.setShadow(false);
- s.scheduleSaveUser(getOwnerUserId());
+ public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
+ if (!mPackageInfo.isShadow()) {
+ return; // Already installed, nothing to do.
}
+ if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+ mPackageName, mPackageUserId));
+ }
+ return; // Not installed, no need to restore yet.
+ }
+ if (!mPackageInfo.hasSignatures()) {
+ s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ + " but signatures not found in the restore data.");
+ onRestoreBlocked(s);
+ return;
+ }
+
+ final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
+ if (!mPackageInfo.canRestoreTo(s, pi)) {
+ // Package is now installed, but can't restore. Let the subclass do the cleanup.
+ onRestoreBlocked(s);
+ return;
+ }
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
+ mPackageUserId, getOwnerUserId()));
+ }
+
+ onRestored(s);
+
+ // Now the package is not shadow.
+ mPackageInfo.setShadow(false);
+
+ s.scheduleSaveUser(mPackageUserId);
}
+ /**
+ * Called when the new package can't be restored because it has a lower version number
+ * or different signatures.
+ */
+ protected abstract void onRestoreBlocked(ShortcutService s);
+
+ /**
+ * Called when the new package is successfully restored.
+ */
+ protected abstract void onRestored(ShortcutService s);
+
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 76a2dfa..5c1e7a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -46,6 +46,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -101,6 +102,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -112,17 +114,16 @@
*
* - Scan and remove orphan bitmaps (just in case).
*
- * - Backup & restore
- *
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
+ *
+ * - Add more call stats.
*/
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
- static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -262,6 +263,26 @@
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
+ // Stats
+ @VisibleForTesting
+ interface Stats {
+ int GET_DEFAULT_HOME = 0;
+ int GET_PACKAGE_INFO = 1;
+ int GET_PACKAGE_INFO_WITH_SIG = 2;
+ int GET_APPLICATION_INFO = 3;
+ int LAUNCHER_PERMISSION_CHECK = 4;
+
+ int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
+ }
+
+ final Object mStatLock = new Object();
+
+ @GuardedBy("mStatLock")
+ private final int[] mCountStats = new int[Stats.COUNT];
+
+ @GuardedBy("mStatLock")
+ private final long[] mDurationStats = new long[Stats.COUNT];
+
public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper());
}
@@ -278,6 +299,13 @@
mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
}
+ void logDurationStat(int statId, long start) {
+ synchronized (mStatLock) {
+ mCountStats[statId]++;
+ mDurationStats[statId] += (System.currentTimeMillis() - start);
+ }
+ }
+
/**
* System service lifecycle.
*/
@@ -822,7 +850,7 @@
@GuardedBy("mLock")
@NonNull
- boolean isUserLoadedLocked(@UserIdInt int userId) {
+ private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
}
@@ -841,19 +869,27 @@
return userPackages;
}
+ void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
+ for (int i = mUsers.size() - 1; i >= 0; i--) {
+ c.accept(mUsers.valueAt(i));
+ }
+ }
+
/** Return the per-user per-package state. */
@GuardedBy("mLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
- return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
+ return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
}
@GuardedBy("mLock")
@NonNull
- ShortcutLauncher getLauncherShortcuts(
- @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
- return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
+ ShortcutLauncher getLauncherShortcutsLocked(
+ @NonNull String packageName, @UserIdInt int ownerUserId,
+ @UserIdInt int launcherUserId) {
+ return getUserShortcutsLocked(ownerUserId)
+ .getLauncherShortcuts(this, packageName, launcherUserId);
}
// === Caller validation ===
@@ -1212,8 +1248,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1249,8 +1283,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1288,8 +1320,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1422,15 +1452,17 @@
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
synchronized (mLock) {
- long start = System.currentTimeMillis();
+ final long start = System.currentTimeMillis();
final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
// Default launcher from package manager.
+ final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
final ComponentName defaultLauncher = injectPackageManagerInternal()
.getHomeActivitiesAsUser(allHomeCandidates, userId);
+ logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
ComponentName detected;
if (defaultLauncher != null) {
@@ -1473,10 +1505,8 @@
lastPriority = ri.priority;
}
}
- final long end = System.currentTimeMillis();
- if (DEBUG) {
- Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
- }
+ logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
+
if (detected != null) {
if (DEBUG) {
Slog.v(TAG, "Detected launcher: " + detected);
@@ -1492,10 +1522,17 @@
// === House keeping ===
+ /**
+ * Remove all the information associated with a package. This will really remove all the
+ * information, including the restore information (i.e. it'll remove packages even if they're
+ * shadow).
+ */
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
-
- // TODO Don't remove shadow packages' information.
+ if (isPackageInstalled(packageName, packageUserId)) {
+ wtf("Package " + packageName + " is still installed for user " + packageUserId);
+ return;
+ }
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
@@ -1504,7 +1541,7 @@
// First, remove the package from the package list (if the package is a publisher).
if (packageUserId == owningUserId) {
- if (mUser.getPackages().remove(packageName) != null) {
+ if (mUser.removePackage(packageName) != null) {
doNotify = true;
}
}
@@ -1515,12 +1552,12 @@
// Then remove pinned shortcuts from all launchers.
final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
for (int i = launchers.size() - 1; i >= 0; i--) {
- launchers.valueAt(i).cleanUpPackage(packageName);
+ launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
}
// Now there may be orphan shortcuts because we removed pinned shortucts at the previous
// step. Remove them too.
- for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
- mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
+ for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) {
+ mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this);
}
scheduleSaveUser(owningUserId);
@@ -1539,6 +1576,7 @@
* Entry point from {@link LauncherApps}.
*/
private class LocalService extends ShortcutServiceInternal {
+
@Override
public List<ShortcutInfo> getShortcuts(int launcherUserId,
@NonNull String callingPackage, long changedSince,
@@ -1551,13 +1589,16 @@
: ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packageName, changedSince,
componentName, queryFlags, userId, ret, cloneFlag);
} else {
final ArrayMap<String, ShortcutPackage> packages =
- getUserShortcutsLocked(userId).getPackages();
+ getUserShortcutsLocked(userId).getAllPackages();
for (int i = packages.size() - 1; i >= 0; i--) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packages.keyAt(i), changedSince,
@@ -1601,6 +1642,9 @@
final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
final ArraySet<String> idSet = new ArraySet<>(ids);
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
getPackageShortcutsLocked(packageName, userId).findAll(
ShortcutService.this, ret,
(ShortcutInfo si) -> idSet.contains(si.getId()),
@@ -1616,13 +1660,16 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
return si != null && si.isPinned();
}
}
- public ShortcutInfo getShortcutInfoLocked(
+ private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1646,9 +1693,8 @@
synchronized (mLock) {
final ShortcutLauncher launcher =
- getLauncherShortcuts(callingPackage, userId, launcherUserId);
-
- launcher.ensureNotShadowAndSave(ShortcutService.this);
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
+ launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
launcher.pinShortcuts(
ShortcutService.this, userId, packageName, shortcutIds);
@@ -1665,6 +1711,9 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1690,6 +1739,9 @@
Preconditions.checkNotNull(shortcut, "shortcut");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
return (shortcutInfo != null && shortcutInfo.hasIconResource())
@@ -1704,6 +1756,9 @@
Preconditions.checkNotNull(shortcutIn, "shortcut");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
@@ -1757,28 +1812,28 @@
* perform cleanup.
*/
@VisibleForTesting
- void cleanupGonePackages(@UserIdInt int userId) {
+ void cleanupGonePackages(@UserIdInt int ownerUserId) {
if (DEBUG) {
- Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
+ Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
}
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
synchronized (mLock) {
- final ShortcutUser user = getUserShortcutsLocked(userId);
+ final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
user.forAllPackageItems(spi -> {
if (spi.getPackageInfo().isShadow()) {
return; // Don't delete shadow information.
}
if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
- return;
+ return; // Package not gone.
}
gonePackages.add(PackageWithUser.of(spi));
});
if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = gonePackages.get(i);
- cleanUpPackageLocked(pu.packageName, userId, pu.userId);
+ cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
}
}
}
@@ -1789,7 +1844,8 @@
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
synchronized (mLock) {
- getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+ forEachLoadedUserLocked(user ->
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
@@ -1798,18 +1854,20 @@
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
-
synchronized (mLock) {
- getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+ forEachLoadedUserLocked(user ->
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
- private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
+ private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
if (DEBUG) {
- Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
+ Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
+ packageUserId));
}
synchronized (mLock) {
- cleanUpPackageLocked(packageName, userId, userId);
+ forEachLoadedUserLocked(user ->
+ cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
}
}
@@ -1836,6 +1894,7 @@
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
+ final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -1847,11 +1906,16 @@
return null;
} finally {
injectRestoreCallingIdentity(token);
+
+ logDurationStat(
+ (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
+ start);
}
}
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+ final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -1861,6 +1925,8 @@
return null;
} finally {
injectRestoreCallingIdentity(token);
+
+ logDurationStat(Stats.GET_APPLICATION_INFO, start);
}
}
@@ -1869,7 +1935,7 @@
return (ai != null) && ((ai.flags & flags) == flags);
}
- private boolean isPackageInstalled(String packageName, int userId) {
+ boolean isPackageInstalled(String packageName, int userId) {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
}
@@ -1879,8 +1945,12 @@
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
+ boolean shouldBackupApp(PackageInfo pi) {
+ return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+ }
+
@Override
- public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
+ public byte[] getBackupPayload(@UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
@@ -1908,7 +1978,7 @@
}
@Override
- public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
+ public void applyRestore(byte[] payload, @UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Restoring user " + userId);
@@ -1923,6 +1993,15 @@
}
synchronized (mLock) {
mUsers.put(userId, user);
+
+ // Then purge all the save images.
+ final File bitmapPath = getUserBitmapFilePath(userId);
+ final boolean success = FileUtils.deleteContents(bitmapPath);
+ if (!success) {
+ Slog.w(TAG, "Failed to delete " + bitmapPath);
+ }
+
+ saveUserLocked(userId);
}
}
@@ -1974,9 +2053,19 @@
pw.print(" Icon format: ");
pw.print(mIconPersistFormat);
pw.print(" Icon quality: ");
- pw.print(mIconPersistQuality);
+ pw.println(mIconPersistQuality);
pw.println();
+ pw.println(" Stats:");
+ synchronized (mStatLock) {
+ final String p = " ";
+ dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
+ dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
+
+ dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
+ dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
+ dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
+ }
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
@@ -1991,6 +2080,15 @@
return tobj.format("%Y-%m-%d %H:%M:%S");
}
+ private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
+ pw.print(prefix);
+ final int count = mCountStats[statId];
+ final long dur = mDurationStats[statId];
+ pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
+ label, count, dur,
+ (count == 0 ? 0 : ((double) dur) / count)));
+ }
+
// === Shell support ===
@Override
@@ -2061,6 +2159,9 @@
case "refresh-default-launcher":
handleRefreshDefaultLauncher();
break;
+ case "unload-user":
+ handleUnloadUser();
+ break;
default:
return handleDefaultCommands(cmd);
}
@@ -2098,6 +2199,10 @@
pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
pw.println(" Refresh the cached default launcher");
pw.println();
+ pw.println("cmd shortcut unload-user [--user USER_ID]");
+ pw.println(" Unload a user from the memory");
+ pw.println(" (This should not affect any observable behavior)");
+ pw.println();
}
private int handleResetThrottling() throws CommandException {
@@ -2169,6 +2274,12 @@
clearLauncher();
showLauncher();
}
+
+ private void handleUnloadUser() throws CommandException {
+ parseOptions(/* takeUser =*/ true);
+
+ ShortcutService.this.handleCleanupUser(mUserId);
+ }
}
// === Unit test support ===
@@ -2202,9 +2313,10 @@
}
final void wtf(String message) {
- Slog.wtf(TAG, message, /* exception= */ null);
+ wtf( message, /* exception= */ null);
}
+ // Injection point.
void wtf(String message, Exception e) {
Slog.wtf(TAG, message, e);
}
@@ -2275,7 +2387,7 @@
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
- final ShortcutPackage pkg = user.getPackages().get(packageName);
+ final ShortcutPackage pkg = user.getAllPackages().get(packageName);
if (pkg == null) return null;
return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 487558f..593f607 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -53,8 +53,8 @@
this.packageName = Preconditions.checkNotNull(packageName);
}
- public static PackageWithUser of(int launcherUserId, String packageName) {
- return new PackageWithUser(launcherUserId, packageName);
+ public static PackageWithUser of(int userId, String packageName) {
+ return new PackageWithUser(userId, packageName);
}
public static PackageWithUser of(ShortcutPackageItem spi) {
@@ -78,12 +78,12 @@
@Override
public String toString() {
- return String.format("{Launcher: %d, %s}", userId, packageName);
+ return String.format("{Package: %d, %s}", userId, packageName);
}
}
@UserIdInt
- final int mUserId;
+ private final int mUserId;
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
@@ -95,10 +95,18 @@
mUserId = userId;
}
- public ArrayMap<String, ShortcutPackage> getPackages() {
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ArrayMap<String, ShortcutPackage> getAllPackages() {
return mPackages;
}
+ public ShortcutPackage removePackage(@NonNull String packageName) {
+ return mPackages.remove(packageName);
+ }
+
public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
return mLaunchers;
}
@@ -113,22 +121,26 @@
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
- public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+ public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
ShortcutPackage ret = mPackages.get(packageName);
if (ret == null) {
ret = new ShortcutPackage(mUserId, packageName);
mPackages.put(packageName, ret);
+ } else {
+ ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
- public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
+ public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
if (ret == null) {
ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
+ } else {
+ ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
@@ -148,14 +160,6 @@
}
}
- public void unshadowPackage(ShortcutService s, @NonNull String packageName,
- @UserIdInt int packageUserId) {
- forPackageItem(packageName, packageUserId, spi -> {
- Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
- spi.ensureNotShadowAndSave(s);
- });
- }
-
public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
Consumer<ShortcutPackageItem> callback) {
forAllPackageItems(spi -> {
@@ -166,6 +170,13 @@
});
}
+ public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
+ @UserIdInt int packageUserId) {
+ forPackageItem(packageName, packageUserId, spi -> {
+ spi.attemptToRestoreIfNeededAndSave(s);
+ });
+ }
+
public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
@@ -229,7 +240,7 @@
s, parser, userId, fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
- ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
+ ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
continue;
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ff5a0f9..7570960 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -538,7 +538,10 @@
@Override
public void onBootPhase(int phase) {
synchronized (mLock) {
- if (phase == PHASE_BOOT_COMPLETED) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ incrementBootCount();
+
+ } else if (phase == PHASE_BOOT_COMPLETED) {
final long now = SystemClock.uptimeMillis();
mBootCompleted = true;
mDirty |= DIRTY_BOOT_COMPLETED;
@@ -553,8 +556,6 @@
}
}
mBootCompletedRunnables = null;
-
- incrementBootCount();
}
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
similarity index 67%
rename from services/core/java/com/android/server/webkit/WebViewUtilityImpl.java
rename to services/core/java/com/android/server/webkit/SystemImpl.java
index aaa7977..6052a6e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -19,15 +19,22 @@
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.content.Context;
+import android.content.pm.IPackageDeleteObserver;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.content.res.XmlResourceParser;
+import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings.Global;
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
-import android.webkit.WebViewFactory;
import android.webkit.WebViewFactory.MissingWebViewPackageException;
+import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import com.android.internal.util.XmlUtils;
@@ -42,8 +49,8 @@
* Default implementation for the WebView preparation Utility interface.
* @hide
*/
-public class WebViewUtilityImpl implements WebViewUtilityInterface {
- private static final String TAG = WebViewUtilityImpl.class.getSimpleName();
+public class SystemImpl implements SystemInterface {
+ private static final String TAG = SystemImpl.class.getSimpleName();
private static final String TAG_START = "webviewproviders";
private static final String TAG_WEBVIEW_PROVIDER = "webviewprovider";
private static final String TAG_PACKAGE_NAME = "packageName";
@@ -158,4 +165,67 @@
} catch (RemoteException e) {
}
}
+
+ @Override
+ public boolean isFallbackLogicEnabled() {
+ // Note that this is enabled by default (i.e. if the setting hasn't been set).
+ return Settings.Global.getInt(AppGlobals.getInitialApplication().getContentResolver(),
+ Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, 1) == 1;
+ }
+
+ @Override
+ public void enableFallbackLogic(boolean enable) {
+ Settings.Global.putInt(AppGlobals.getInitialApplication().getContentResolver(),
+ Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, enable ? 1 : 0);
+ }
+
+ @Override
+ public void uninstallAndDisablePackageForAllUsers(Context context, String packageName) {
+ context.getPackageManager().deletePackage(packageName,
+ new IPackageDeleteObserver.Stub() {
+ public void packageDeleted(String packageName, int returnCode) {
+ // Ignore returnCode since the deletion could fail, e.g. we might be trying
+ // to delete a non-updated system-package (and we should still disable the
+ // package)
+ enablePackageForAllUsers(context, packageName, false);
+ }
+ }, PackageManager.DELETE_SYSTEM_APP | PackageManager.DELETE_ALL_USERS);
+ }
+
+ @Override
+ public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
+ UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
+ for(UserInfo userInfo : userManager.getUsers()) {
+ enablePackageForUser(packageName, enable, userInfo.id);
+ }
+ }
+
+ @Override
+ public void enablePackageForUser(String packageName, boolean enable, int userId) {
+ try {
+ AppGlobals.getPackageManager().setApplicationEnabledSetting(
+ packageName,
+ enable ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0,
+ userId, null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Tried to disable " + packageName + " for user " + userId + ": " + e);
+ }
+ }
+
+ @Override
+ public boolean systemIsDebuggable() {
+ return Build.IS_DEBUGGABLE;
+ }
+
+ @Override
+ public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
+ throws NameNotFoundException {
+ PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
+ }
+
+ // flags declaring we want extra info from the package manager for webview providers
+ private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
+ | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUtilityInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
similarity index 67%
rename from services/core/java/com/android/server/webkit/WebViewUtilityInterface.java
rename to services/core/java/com/android/server/webkit/SystemInterface.java
index 1919f40..b5eb0a7 100644
--- a/services/core/java/com/android/server/webkit/WebViewUtilityInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -16,22 +16,35 @@
package com.android.server.webkit;
-import android.webkit.WebViewProviderInfo;
import android.content.Context;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.webkit.WebViewProviderInfo;
/**
- * Utility interface for the WebViewUpdateService.
+ * System interface for the WebViewUpdateService.
* This interface provides a way to test the WebView preparation mechanism - during normal use this
* interface is implemented using calls to the Android framework, but by providing an alternative
* implementation we can test the WebView preparation logic without reaching other framework code.
+ *
* @hide
*/
-public interface WebViewUtilityInterface {
+public interface SystemInterface {
public WebViewProviderInfo[] getWebViewPackages();
public int onWebViewProviderChanged(PackageInfo packageInfo);
public String getUserChosenWebViewProvider(Context context);
public void updateUserSetting(Context context, String newProviderName);
public void killPackageDependents(String packageName);
+
+ public boolean isFallbackLogicEnabled();
+ public void enableFallbackLogic(boolean enable);
+
+ public void uninstallAndDisablePackageForAllUsers(Context context, String packageName);
+ public void enablePackageForAllUsers(Context context, String packageName, boolean enable);
+ public void enablePackageForUser(String packageName, boolean enable, int userId);
+
+ public boolean systemIsDebuggable();
+ public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
+ throws NameNotFoundException;
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index c4f9cc1..a54c542 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,30 +16,19 @@
package com.android.server.webkit;
-import android.app.ActivityManagerNative;
-import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
-import android.content.pm.UserInfo;
import android.os.Binder;
-import android.os.Build;
import android.os.PatternMatcher;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings.Global;
-import android.provider.Settings;
-import android.util.AndroidRuntimeException;
import android.util.Base64;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
@@ -52,7 +41,6 @@
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
/**
@@ -77,11 +65,11 @@
private PackageInfo mCurrentWebViewPackage = null;
private BroadcastReceiver mWebViewUpdatedReceiver;
- private WebViewUtilityInterface mWebViewUtility;
+ private SystemInterface mSystemInterface;
public WebViewUpdateService(Context context) {
super(context);
- mWebViewUtility = new WebViewUtilityImpl();
+ mSystemInterface = new SystemImpl();
}
@Override
@@ -103,7 +91,7 @@
// Ensure that we only heed PACKAGE_CHANGED intents if they change an entire
// package, not just a component
if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) {
- if (!WebViewFactory.entirePackageChanged(intent)) {
+ if (!entirePackageChanged(intent)) {
return;
}
}
@@ -117,7 +105,7 @@
updateFallbackState(context, intent);
- for (WebViewProviderInfo provider : mWebViewUtility.getWebViewPackages()) {
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
String webviewPackage = "package:" + provider.packageName;
if (webviewPackage.equals(intent.getDataString())) {
@@ -155,7 +143,7 @@
// package that was not the previous provider then we must kill
// packages dependent on the old package ourselves. The framework
// only kills dependents of packages that are being removed.
- mWebViewUtility.killPackageDependents(oldProviderName);
+ mSystemInterface.killPackageDependents(oldProviderName);
}
return;
}
@@ -168,7 +156,7 @@
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
// Make sure we only receive intents for WebView packages from our config file.
- for (WebViewProviderInfo provider : mWebViewUtility.getWebViewPackages()) {
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
}
getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
@@ -184,7 +172,7 @@
for (WebViewProviderInfo provider : providers) {
if (provider.availableByDefault && !provider.isFallback) {
try {
- PackageInfo packageInfo = getPackageInfoForProvider(provider);
+ PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
if (isEnabledPackage(packageInfo) && isValidProvider(provider, packageInfo)) {
return true;
}
@@ -196,29 +184,17 @@
return false;
}
- private static void enablePackageForUser(String packageName, boolean enable, int userId) {
- try {
- AppGlobals.getPackageManager().setApplicationEnabledSetting(
- packageName,
- enable ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0,
- userId, null);
- } catch (RemoteException e) {
- Slog.w(TAG, "Tried to disable " + packageName + " for user " + userId + ": " + e);
- }
- }
-
/**
* Called when a new user has been added to update the state of its fallback package.
*/
void handleNewUser(int userId) {
- if (!isFallbackLogicEnabled()) return;
+ if (!mSystemInterface.isFallbackLogicEnabled()) return;
- WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();
+ WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
if (fallbackProvider == null) return;
- enablePackageForUser(fallbackProvider.packageName,
+ mSystemInterface.enablePackageForUser(fallbackProvider.packageName,
!existsValidNonFallbackProvider(webviewProviders), userId);
}
@@ -228,9 +204,9 @@
* otherwise, enable the fallback package.
*/
void updateFallbackState(final Context context, final Intent intent) {
- if (!isFallbackLogicEnabled()) return;
+ if (!mSystemInterface.isFallbackLogicEnabled()) return;
- WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();
+ WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
if (intent != null && (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)
|| intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED))) {
@@ -257,7 +233,8 @@
boolean isFallbackEnabled = false;
try {
- isFallbackEnabled = isEnabledPackage(getPackageInfoForProvider(fallbackProvider));
+ isFallbackEnabled =
+ isEnabledPackage(mSystemInterface.getPackageInfoForProvider(fallbackProvider));
} catch (NameNotFoundException e) {
}
@@ -265,45 +242,17 @@
// During an OTA the primary user's WebView state might differ from other users', so
// ignore the state of that user during boot.
&& (isFallbackEnabled || intent == null)) {
- // Uninstall and disable fallback package for all users.
- context.getPackageManager().deletePackage(fallbackProvider.packageName,
- new IPackageDeleteObserver.Stub() {
- public void packageDeleted(String packageName, int returnCode) {
- // Ignore returnCode since the deletion could fail, e.g. we might be trying
- // to delete a non-updated system-package (and we should still disable the
- // package)
- UserManager userManager =
- (UserManager)context.getSystemService(Context.USER_SERVICE);
- // Disable the fallback package for all users.
- for(UserInfo userInfo : userManager.getUsers()) {
- enablePackageForUser(packageName, false, userInfo.id);
- }
- }
- }, PackageManager.DELETE_SYSTEM_APP | PackageManager.DELETE_ALL_USERS);
+ mSystemInterface.uninstallAndDisablePackageForAllUsers(context,
+ fallbackProvider.packageName);
} else if (!existsValidNonFallbackProvider
// During an OTA the primary user's WebView state might differ from other users', so
// ignore the state of that user during boot.
&& (!isFallbackEnabled || intent==null)) {
// Enable the fallback package for all users.
- UserManager userManager =
- (UserManager)context.getSystemService(Context.USER_SERVICE);
- for(UserInfo userInfo : userManager.getUsers()) {
- enablePackageForUser(fallbackProvider.packageName, true, userInfo.id);
- }
+ mSystemInterface.enablePackageForAllUsers(context, fallbackProvider.packageName, true);
}
}
- private static boolean isFallbackLogicEnabled() {
- // Note that this is enabled by default (i.e. if the setting hasn't been set).
- return Settings.Global.getInt(AppGlobals.getInitialApplication().getContentResolver(),
- Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, 1) == 1;
- }
-
- private static void enableFallbackLogic(boolean enable) {
- Settings.Global.putInt(AppGlobals.getInitialApplication().getContentResolver(),
- Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, enable ? 1 : 0);
- }
-
/**
* Returns the only fallback provider, or null if there is none.
*/
@@ -317,9 +266,9 @@
}
private boolean isFallbackPackage(String packageName) {
- if (packageName == null || !isFallbackLogicEnabled()) return false;
+ if (packageName == null || !mSystemInterface.isFallbackLogicEnabled()) return false;
- WebViewProviderInfo[] webviewPackages = mWebViewUtility.getWebViewPackages();
+ WebViewProviderInfo[] webviewPackages = mSystemInterface.getWebViewPackages();
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewPackages);
return (fallbackProvider != null
&& packageName.equals(fallbackProvider.packageName));
@@ -355,13 +304,13 @@
PackageInfo newPackage = null;
synchronized(this) {
oldPackage = mCurrentWebViewPackage;
- mWebViewUtility.updateUserSetting(getContext(), newProviderName);
+ mSystemInterface.updateUserSetting(getContext(), newProviderName);
try {
newPackage = findPreferredWebViewPackage();
if (oldPackage != null && newPackage.packageName.equals(oldPackage.packageName)) {
// If we don't perform the user change, revert the settings change.
- mWebViewUtility.updateUserSetting(getContext(), newPackage.packageName);
+ mSystemInterface.updateUserSetting(getContext(), newPackage.packageName);
return newPackage.packageName;
}
} catch (WebViewFactory.MissingWebViewPackageException e) {
@@ -375,7 +324,7 @@
}
// Kill apps using the old provider
if (oldPackage != null) {
- mWebViewUtility.killPackageDependents(oldPackage.packageName);
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
}
return newPackage.packageName;
}
@@ -389,14 +338,14 @@
mAnyWebViewInstalled = true;
if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
mCurrentWebViewPackage = newPackage;
- mWebViewUtility.updateUserSetting(getContext(), newPackage.packageName);
+ mSystemInterface.updateUserSetting(getContext(), newPackage.packageName);
// The relro creations might 'finish' (not start at all) before
// WebViewFactory.onWebViewProviderChanged which means we might not know the number
// of started creations before they finish.
mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
mNumRelroCreationsFinished = 0;
- mNumRelroCreationsStarted = mWebViewUtility.onWebViewProviderChanged(newPackage);
+ mNumRelroCreationsStarted = mSystemInterface.onWebViewProviderChanged(newPackage);
// If the relro creations finish before we know the number of started creations we
// will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -407,11 +356,12 @@
}
private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
- WebViewProviderInfo[] allProviders = mWebViewUtility.getWebViewPackages();
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
List<ProviderAndPackageInfo> providers = new ArrayList<>();
for(int n = 0; n < allProviders.length; n++) {
try {
- PackageInfo packageInfo = getPackageInfoForProvider(allProviders[n]);
+ PackageInfo packageInfo =
+ mSystemInterface.getPackageInfoForProvider(allProviders[n]);
if (isValidProvider(allProviders[n], packageInfo)) {
providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
}
@@ -454,7 +404,7 @@
private PackageInfo findPreferredWebViewPackage() {
ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
- String userChosenProvider = mWebViewUtility.getUserChosenWebViewProvider(getContext());
+ String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(getContext());
// If the user has chosen provider, use that
for (ProviderAndPackageInfo providerAndPackage : providers) {
@@ -483,10 +433,11 @@
"Could not find a loadable WebView package");
}
+
/**
* Returns whether this provider is valid for use as a WebView provider.
*/
- private static boolean isValidProvider(WebViewProviderInfo configInfo,
+ public boolean isValidProvider(WebViewProviderInfo configInfo,
PackageInfo packageInfo) {
if (providerHasValidSignature(configInfo, packageInfo) &&
WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
@@ -495,10 +446,11 @@
return false;
}
- private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+ private boolean providerHasValidSignature(WebViewProviderInfo provider,
PackageInfo packageInfo) {
- if (Build.IS_DEBUGGABLE)
+ if (mSystemInterface.systemIsDebuggable()) {
return true;
+ }
Signature[] packageSignatures;
// If no signature is declared, instead check whether the package is included in the
// system.
@@ -523,20 +475,22 @@
* Returns whether the given package is enabled.
* This state can be changed by the user from Settings->Apps
*/
- private static boolean isEnabledPackage(PackageInfo packageInfo) {
+ public boolean isEnabledPackage(PackageInfo packageInfo) {
return packageInfo.applicationInfo.enabled;
}
- private static PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
- throws NameNotFoundException {
- PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
- return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
+ /**
+ * Returns whether the entire package from an ACTION_PACKAGE_CHANGED intent was changed (rather
+ * than just one of its components).
+ * @hide
+ */
+ public static boolean entirePackageChanged(Intent intent) {
+ String[] componentList =
+ intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ return Arrays.asList(componentList).contains(
+ intent.getDataString().substring("package:".length()));
}
- // flags declaring we want extra info from the package manager for webview providers
- private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
- | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
-
/**
* Returns whether WebView is ready and is not going to go through its preparation phase again
* directly.
@@ -669,7 +623,7 @@
@Override // Binder call
public WebViewProviderInfo[] getAllWebViewPackages() {
- return WebViewUpdateService.this.mWebViewUtility.getWebViewPackages();
+ return WebViewUpdateService.this.mSystemInterface.getWebViewPackages();
}
@Override // Binder call
@@ -699,7 +653,7 @@
throw new SecurityException(msg);
}
- WebViewUpdateService.enableFallbackLogic(enable);
+ WebViewUpdateService.this.mSystemInterface.enableFallbackLogic(enable);
}
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index b501398..bae628a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -39,6 +39,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -1213,6 +1214,8 @@
window.type = windowState.mAttrs.type;
window.layer = windowState.mLayer;
window.token = windowState.mClient.asBinder();
+ window.title = windowState.mAttrs.getTitle();
+ window.accessibilityIdOfAnchor = windowState.mAttrs.accessibilityIdOfAnchor;
WindowState attachedWindow = windowState.mAttachedWindow;
if (attachedWindow != null) {
@@ -1285,6 +1288,12 @@
&& !oldWindow.childTokens.equals(newWindow.childTokens)) {
return true;
}
+ if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
+ return true;
+ }
+ if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
+ return true;
+ }
return false;
}
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 97d0ae0..3ec02b9 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -183,7 +183,12 @@
for (int i = mState.size() - 1; i >= 0; i--) {
DimLayer.DimLayerUser user = mState.keyAt(i);
- if (user.isFullscreen()) {
+ DimLayerState state = mState.valueAt(i);
+ // We have to check that we are acutally the shared fullscreen layer
+ // for this path. If we began as non fullscreen and became fullscreen
+ // (e.g. Docked stack closing), then we may not be the shared layer
+ // and we have to make sure we always animate the layer.
+ if (user.isFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
fullScreen = i;
if (mState.valueAt(i).continueDimming) {
fullScreenAndDimming = i;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1f03c04..eea0ca0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -425,7 +425,7 @@
if (mFullscreen
|| !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
|| displayContent == null
- || displayContent.getDockedStackLocked() != null) {
+ || displayContent.getDockedStackVisibleForUserLocked() != null) {
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 0bf7102..bb0ec4c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -128,6 +128,10 @@
// in which case a second window animation would cause jitter.
private boolean mFreezeMovementAnimations = false;
+ // Temporary storage for the new bounds that should be used after the configuration change.
+ // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
+ private final Rect mBoundsAfterRotation = new Rect();
+
TaskStack(WindowManagerService service, int stackId) {
mService = service;
mStackId = stackId;
@@ -343,28 +347,28 @@
setBounds(mTmpRect2);
} else {
mLastUpdateDisplayInfoRotation = newRotation;
- updateBoundsAfterRotation();
+ updateBoundsAfterRotation(true);
}
}
- void onConfigurationChanged() {
+ boolean onConfigurationChanged() {
mLastConfigChangedRotation = getDisplayInfo().rotation;
- updateBoundsAfterRotation();
+ return updateBoundsAfterRotation(false);
}
- void updateBoundsAfterRotation() {
+ boolean updateBoundsAfterRotation(boolean scheduleResize) {
if (mLastConfigChangedRotation != mLastUpdateDisplayInfoRotation) {
// We wait for the rotation values after configuration change and display info. update
// to be equal before updating the bounds due to rotation change otherwise things might
// get out of alignment...
- return;
+ return false;
}
final int newRotation = getDisplayInfo().rotation;
if (mRotation == newRotation) {
// Nothing to do here if the rotation didn't change
- return;
+ return false;
}
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
@@ -373,11 +377,22 @@
snapDockedStackAfterRotation(mTmpRect2);
}
- // Post message to inform activity manager of the bounds change simulating
- // a one-way call. We do this to prevent a deadlock between window manager
- // lock and activity manager lock been held.
- mService.mH.obtainMessage(
- RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2).sendToTarget();
+ if (scheduleResize) {
+ // Post message to inform activity manager of the bounds change simulating
+ // a one-way call. We do this to prevent a deadlock between window manager
+ // lock and activity manager lock been held.
+ mService.mH.obtainMessage(RESIZE_STACK, mStackId,
+ 0 /*allowResizeInDockedMode*/, mTmpRect2).sendToTarget();
+ } else {
+ mBoundsAfterRotation.set(mTmpRect2);
+ }
+
+ return true;
+ }
+
+ void getBoundsForNewConfiguration(Rect outBounds) {
+ outBounds.set(mBoundsAfterRotation);
+ mBoundsAfterRotation.setEmpty();
}
/**
@@ -869,6 +884,10 @@
}
}
+ boolean isAdjustedForMinimizedDock() {
+ return mMinimizeAmount != 0f;
+ }
+
private boolean adjustForIME(final WindowState imeWin) {
final int dockedSide = getDockSide();
final boolean dockedTopOrBottom = dockedSide == DOCKED_TOP || dockedSide == DOCKED_BOTTOM;
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index f76f03f..2437ff5 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -203,6 +203,12 @@
// the divider sometimes overlaps the app windows.
layer++;
layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
+
+ // If we have a dock divider ensure the Input Method is above it.
+ if (mDockDivider != null && mService.mInputMethodWindow != null) {
+ layer = assignAndIncreaseLayerIfNeeded(mService.mInputMethodWindow, layer);
+ }
+
// 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.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dcb4a63..fbe2803 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -125,6 +125,7 @@
import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -515,6 +516,8 @@
private final SparseIntArray mTmpTaskIds = new SparseIntArray();
+ private final ArrayList<Integer> mChangedStackList = new ArrayList();
+
boolean mForceResizableTasks = false;
int getDragLayerLocked() {
@@ -1370,7 +1373,7 @@
// needs to sit above the dock divider, so it doesn't get cut in half. We make the dock
// divider be a target for IME, so this relationship can occur naturally.
if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)
- || type == TYPE_APPLICATION_STARTING || type == TYPE_DOCK_DIVIDER) {
+ || type == TYPE_APPLICATION_STARTING) {
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG_WM, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
if (!w.isVisibleOrAdding()) {
@@ -3398,7 +3401,8 @@
}
}
- if (isStackVisibleLocked(DOCKED_STACK_ID)
+ if ((isStackVisibleLocked(DOCKED_STACK_ID)
+ && !mStackIdToStack.get(DOCKED_STACK_ID).isAdjustedForMinimizedDock())
|| 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.
@@ -3580,7 +3584,7 @@
}
@Override
- public void setNewConfiguration(Configuration config) {
+ public int[] setNewConfiguration(Configuration config) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setNewConfiguration()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3592,16 +3596,30 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- onConfigurationChanged();
- mWindowPlacerLocked.performSurfacePlacement();
+ return onConfigurationChanged();
}
}
- private void onConfigurationChanged() {
+ @Override
+ public Rect getBoundsForNewConfiguration(int stackId) {
+ synchronized(mWindowMap) {
+ final TaskStack stack = mStackIdToStack.get(stackId);
+ final Rect outBounds = new Rect();
+ stack.getBoundsForNewConfiguration(outBounds);
+ return outBounds;
+ }
+ }
+
+ private int[] onConfigurationChanged() {
+ mChangedStackList.clear();
for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) {
final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
- stack.onConfigurationChanged();
+ if (stack.onConfigurationChanged()) {
+ mChangedStackList.add(stack.mStackId);
+ }
}
+ return mChangedStackList.isEmpty() ?
+ null : ArrayUtils.convertToIntArray(mChangedStackList);
}
@Override
@@ -4117,6 +4135,14 @@
for (int i = 0; i < windowsCount; i++) {
WindowState win = wtoken.allAppWindows.get(i);
if (win == wtoken.startingWindow) {
+ // Starting window that's exiting will be removed when the animation
+ // finishes. Mark all relevant flags for that finishExit will proceed
+ // all the way to actually remove it.
+ if (!visible && win.isVisibleNow() && wtoken.mAppAnimator.isAnimating()) {
+ win.mAnimatingExit = true;
+ win.mRemoveOnExit = true;
+ win.mWindowRemovalAllowed = true;
+ }
continue;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 57ead8b..f0f292a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -670,10 +670,18 @@
mContainingFrame.bottom = mContainingFrame.top + frozen.height();
}
final WindowState imeWin = mService.mInputMethodWindow;
- if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this
- && mContainingFrame.bottom > cf.bottom) {
- // IME is up and obscuring this window. Adjust the window position so it is visible.
- mContainingFrame.top -= mContainingFrame.bottom - cf.bottom;
+ // IME is up and obscuring this window. Adjust the window position so it is visible.
+ if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) {
+ if (windowsAreFloating && mContainingFrame.bottom > cf.bottom) {
+ // In freeform we want to move the top up directly.
+ // TODO: Investigate why this is cf not pf.
+ mContainingFrame.top -= mContainingFrame.bottom - cf.bottom;
+ } else if (mContainingFrame.bottom > pf.bottom) {
+ // But in docked we want to behave like fullscreen
+ // and behave as if the task were given smaller bounds
+ // for the purposes of layout.
+ mContainingFrame.bottom = pf.bottom;
+ }
}
if (windowsAreFloating) {
@@ -1878,6 +1886,11 @@
}
private boolean shouldSaveSurface() {
+ if (mWinAnimator.mSurfaceController == null) {
+ // Don't bother if the surface controller is gone for any reason.
+ return false;
+ }
+
if ((mAttrs.flags & FLAG_SECURE) != 0) {
// We don't save secure surfaces since their content shouldn't be shown while the app
// isn't on screen and content might leak through during the transition animation with
@@ -1951,10 +1964,18 @@
return;
}
mSurfaceSaved = false;
- setHasSurface(true);
- mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
- Slog.v(TAG, "Restoring saved surface: " + this);
+ if (mWinAnimator.mSurfaceController != null) {
+ setHasSurface(true);
+ mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG, "Restoring saved surface: " + this);
+ }
+ } else {
+ // mSurfaceController shouldn't be null if mSurfaceSaved was still true at
+ // this point. Even if we destroyed the saved surface because of rotation
+ // or resize, mSurfaceSaved flag should have been cleared. So this is a wtf.
+ Slog.wtf(TAG, "Failed to restore saved surface: surface gone! " + this);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
new file mode 100644
index 0000000..83a59fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class BuzzBeepBlinkTest extends AndroidTestCase {
+
+ @Mock AudioManager mAudioManager;
+ @Mock Vibrator mVibrator;
+ @Mock android.media.IRingtonePlayer mRingtonePlayer;
+ @Mock Handler mHandler;
+
+ private NotificationManagerService mService;
+ private String mPkg = "com.android.server.notification";
+ private int mId = 1001;
+ private int mOtherId = 1002;
+ private String mTag = null;
+ private int mUid = 1000;
+ private int mPid = 2000;
+ private int mScore = 10;
+ private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Override
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
+ when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+
+ mService = new NotificationManagerService(getContext());
+ mService.setAudioManager(mAudioManager);
+ mService.setVibrator(mVibrator);
+ mService.setSystemReady(true);
+ mService.setHandler(mHandler);
+ }
+
+ //
+ // Convenience functions for creating notification records
+ //
+
+ private NotificationRecord getNoisyOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ true /* noisy */, true /* buzzy*/);
+ }
+
+ private NotificationRecord getBeepyNotification() {
+ return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getBeepyOnceNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ true /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getQuietNotification() {
+ return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ false /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getQuietOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ false /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getQuietOnceNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ false /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getInsistentBeepyNotification() {
+ return getNotificationRecord(mId, true /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/);
+ }
+
+ private NotificationRecord getBuzzyNotification() {
+ return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ false /* noisy */, true /* buzzy*/);
+ }
+
+ private NotificationRecord getBuzzyOnceNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ false /* noisy */, true /* buzzy*/);
+ }
+
+ private NotificationRecord getInsistentBuzzyNotification() {
+ return getNotificationRecord(mId, true /* insistent */, false /* once */,
+ false /* noisy */, true /* buzzy*/);
+ }
+
+ private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+ boolean noisy, boolean buzzy) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setOnlyAlertOnce(once);
+
+ int defaults = 0;
+ if (noisy) {
+ defaults |= Notification.DEFAULT_SOUND;
+ }
+ if (buzzy) {
+ defaults |= Notification.DEFAULT_VIBRATE;
+ }
+ builder.setDefaults(defaults);
+
+ Notification n = builder.build();
+ if (insistent) {
+ n.flags |= Notification.FLAG_INSISTENT;
+ }
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid,
+ mScore, n, mUser, System.currentTimeMillis());
+ return new NotificationRecord(getContext(), sbn);
+ }
+
+ //
+ // Convenience functions for interacting with mocks
+ //
+
+ private void verifyNeverBeep() throws RemoteException {
+ verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
+ anyBoolean(), (AudioAttributes) anyObject());
+ }
+
+ private void verifyBeep() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
+ eq(true), (AudioAttributes) anyObject());
+ }
+
+ private void verifyBeepLooped() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
+ eq(false), (AudioAttributes) anyObject());
+ }
+
+ private void verifyNeverStopAudio() throws RemoteException {
+ verify(mRingtonePlayer, never()).stopAsync();
+ }
+
+ private void verifyStopAudio() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).stopAsync();
+ }
+
+ private void verifyNeverVibrate() {
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
+ anyInt(), (AudioAttributes) anyObject());
+ }
+
+ private void verifyVibrate() {
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
+ eq(-1), (AudioAttributes) anyObject());
+ }
+
+ private void verifyVibrateLooped() {
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
+ eq(0), (AudioAttributes) anyObject());
+ }
+
+ private void verifyStopVibrate() {
+ verify(mVibrator, times(1)).cancel();
+ }
+
+ private void verifyNeverStopVibrate() throws RemoteException {
+ verify(mVibrator, never()).cancel();
+ }
+
+ @SmallTest
+ public void testBeep() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyBeepLooped();
+ verifyNeverVibrate();
+ }
+
+ //
+ // Tests
+ //
+
+ @SmallTest
+ public void testBeepInsistently() throws Exception {
+ NotificationRecord r = getInsistentBeepyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyBeep();
+ }
+
+ @SmallTest
+ public void testNoInterruptionForMin() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ r.setImportance(Ranking.IMPORTANCE_MIN, "foo");
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ verifyNeverVibrate();
+ }
+
+ @SmallTest
+ public void testNoInterruptionForIntercepted() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ r.setIntercepted(true);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ verifyNeverVibrate();
+ }
+
+ @SmallTest
+ public void testBeepTwice() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep
+ r.isUpdate = true;
+ mService.buzzBeepBlinkLocked(r);
+ verifyBeepLooped();
+ }
+
+ @SmallTest
+ public void testHonorAlertOnlyOnceForBeep() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getBeepyOnceNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should not beep
+ mService.buzzBeepBlinkLocked(s);
+ verifyNeverBeep();
+ }
+
+ @SmallTest
+ public void testNoisyUpdateDoesNotCancelAudio() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+ r.isUpdate = true;
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverStopAudio();
+ }
+
+ @SmallTest
+ public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getBeepyOnceNotification();
+ s.isUpdate = true;
+
+ mService.buzzBeepBlinkLocked(r);
+ mService.buzzBeepBlinkLocked(s);
+
+ verifyNeverStopAudio();
+ }
+
+ @SmallTest
+ public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getQuietNotification();
+ s.isUpdate = true;
+ NotificationRecord other = getNoisyOtherNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ mService.buzzBeepBlinkLocked(other); // this takes the audio stream
+ Mockito.reset(mRingtonePlayer);
+
+ // should not stop noise, since we no longer own it
+ mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
+ verifyNeverStopAudio();
+ }
+
+ @SmallTest
+ public void testQuietInterloperDoesNotCancelAudio() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord other = getQuietOtherNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mRingtonePlayer);
+
+ // should not stop noise, since it does not own it
+ mService.buzzBeepBlinkLocked(other);
+ verifyNeverStopAudio();
+ }
+
+ @SmallTest
+ public void testQuietUpdateCancelsAudio() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getQuietNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mRingtonePlayer);
+
+ // quiet update should stop making noise
+ mService.buzzBeepBlinkLocked(s);
+ verifyStopAudio();
+ }
+
+ @SmallTest
+ public void testQuietOnceUpdateCancelsAudio() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getQuietOnceNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mRingtonePlayer);
+
+ // stop making noise - this is a weird corner case, but quiet should override once
+ mService.buzzBeepBlinkLocked(s);
+ verifyStopAudio();
+ }
+
+ @SmallTest
+ public void testDemoteSoundToVibrate() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+
+ // the phone is quiet
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ verifyVibrate();
+ }
+
+ @SmallTest
+ public void testDemotInsistenteSoundToVibrate() throws Exception {
+ NotificationRecord r = getInsistentBeepyNotification();
+
+ // the phone is quiet
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyVibrateLooped();
+ }
+
+ @SmallTest
+ public void testVibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ verifyVibrate();
+ }
+
+ @SmallTest
+ public void testInsistenteVibrate() throws Exception {
+ NotificationRecord r = getInsistentBuzzyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+ verifyVibrateLooped();
+ }
+
+ @SmallTest
+ public void testVibratTwice() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mVibrator);
+
+ // update should vibrate
+ r.isUpdate = true;
+ mService.buzzBeepBlinkLocked(r);
+ verifyVibrate();
+ }
+
+ @SmallTest
+ public void testHonorAlertOnlyOnceForBuzz() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord s = getBuzzyOnceNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mVibrator);
+
+ // update should not beep
+ mService.buzzBeepBlinkLocked(s);
+ verifyNeverVibrate();
+ }
+
+ @SmallTest
+ public void testNoisyUpdateDoesNotCancelVibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+ r.isUpdate = true;
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverStopVibrate();
+ }
+
+ @SmallTest
+ public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord s = getBuzzyOnceNotification();
+ s.isUpdate = true;
+
+ mService.buzzBeepBlinkLocked(r);
+ mService.buzzBeepBlinkLocked(s);
+
+ verifyNeverStopVibrate();
+ }
+
+ @SmallTest
+ public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord s = getQuietNotification();
+ s.isUpdate = true;
+ NotificationRecord other = getNoisyOtherNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream
+ Mockito.reset(mVibrator);
+
+ // should not stop vibrate, since we no longer own it
+ mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
+ verifyNeverStopVibrate();
+ }
+
+ @SmallTest
+ public void testQuietInterloperDoesNotCancelVibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord other = getQuietOtherNotification();
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mVibrator);
+
+ // should not stop noise, since it does not own it
+ mService.buzzBeepBlinkLocked(other);
+ verifyNeverStopVibrate();
+ }
+
+ @SmallTest
+ public void testQuietUpdateCancelsVibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord s = getQuietNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+
+ // quiet update should stop making noise
+ mService.buzzBeepBlinkLocked(s);
+ verifyStopVibrate();
+ }
+
+ @SmallTest
+ public void testQuietOnceUpdateCancelsvibrate() throws Exception {
+ NotificationRecord r = getBuzzyNotification();
+ NotificationRecord s = getQuietOnceNotification();
+ s.isUpdate = true;
+
+ // set up internal state
+ mService.buzzBeepBlinkLocked(r);
+ Mockito.reset(mVibrator);
+
+ // stop making noise - this is a weird corner case, but quiet should override once
+ mService.buzzBeepBlinkLocked(s);
+ verifyStopVibrate();
+ }
+
+ @SmallTest
+ public void testQuietUpdateCancelsDemotedVibrate() throws Exception {
+ NotificationRecord r = getBeepyNotification();
+ NotificationRecord s = getQuietNotification();
+
+ // the phone is quiet
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
+ when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ // quiet update should stop making noise
+ mService.buzzBeepBlinkLocked(s);
+ verifyStopVibrate();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
deleted file mode 100644
index c44ffa4..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
+++ /dev/null
@@ -1,271 +0,0 @@
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.PersistableBundle;
-import android.test.AndroidTestCase;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.testutis.TestUtils;
-
-/**
- * Tests for {@link ShortcutInfo}.
-
- m FrameworksServicesTests &&
- adb install \
- -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
- adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
- -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
-
- */
-public class ShortcutInfoTest extends AndroidTestCase {
-
- public void testMissingMandatoryFields() {
- TestUtils.assertExpectException(
- IllegalArgumentException.class,
- "ID must be provided",
- () -> new ShortcutInfo.Builder(mContext).build());
- TestUtils.assertExpectException(
- IllegalArgumentException.class,
- "title must be provided",
- () -> new ShortcutInfo.Builder(mContext).setId("id").build()
- .enforceMandatoryFields());
- TestUtils.assertExpectException(
- NullPointerException.class,
- "Intent must be provided",
- () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
- .enforceMandatoryFields());
- }
-
- private ShortcutInfo parceled(ShortcutInfo si) {
- Parcel p = Parcel.obtain();
- p.writeParcelable(si, 0);
- p.setDataPosition(0);
- ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
- p.recycle();
- return si2;
- }
-
- private Intent makeIntent(String action, Object... bundleKeysAndValues) {
- final Intent intent = new Intent(action);
- intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
- return intent;
- }
-
- public void testParcel() {
- ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setTitle("title")
- .setIntent(makeIntent("action"))
- .build());
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals("action", si.getIntent().getAction());
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
-
- si = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- si.addFlags(ShortcutInfo.FLAG_PINNED);
- si.setBitmapPath("abc");
- si.setIconResourceId(456);
-
- si = parceled(si);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals("content://a.b.c/", si.getIcon().getUriString());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
- }
-
- public void testClone() {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals("content://a.b.c/", si.getIcon().getUriString());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(null, si.getIntent());
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(null, si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals(null, si.getTitle());
- assertEquals(null, si.getText());
- assertEquals(null, si.getIntent());
- assertEquals(0, si.getWeight());
- assertEquals(null, si.getExtras());
-
- assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
- }
-
-
- public void testCopyNonNullFieldsFrom() {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si;
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setActivityComponent(new ComponentName("x", "y")).build());
- assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
- assertEquals("content://x.y.z/", si.getIcon().getUriString());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setTitle("xyz").build());
- assertEquals("xyz", si.getTitle());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setText("xxx").build());
- assertEquals("xxx", si.getText());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIntent(makeIntent("action2")).build());
- assertEquals("action2", si.getIntent().getAction());
- assertEquals(null, si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIntent(makeIntent("action3", "key", "x")).build());
- assertEquals("action3", si.getIntent().getAction());
- assertEquals("x", si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setWeight(999).build());
- assertEquals(999, si.getWeight());
-
-
- PersistableBundle pb2 = new PersistableBundle();
- pb2.putInt("x", 99);
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setExtras(pb2).build());
- assertEquals(99, si.getExtras().getInt("x"));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 5d29242..baa5d36 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -16,8 +16,11 @@
package com.android.server.pm;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -46,6 +49,7 @@
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.Signature;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
@@ -57,7 +61,9 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -66,6 +72,7 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.frameworks.servicestests.R;
@@ -76,6 +83,7 @@
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
+import com.android.server.testutis.TestUtils;
import libcore.io.IoUtils;
@@ -96,6 +104,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
/**
* Tests for ShortcutService and ShortcutManager.
@@ -107,10 +117,8 @@
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
* TODO: Add checks with assertAllNotHaveIcon()
- *
- * TODO: separate, detailed tests for ShortcutInfo (CTS?) *
- *
- * TODO: Cross-user test (do in CTS?)
+ * TODO: Detailed test for hasShortcutPermissionInner().
+ * TODO: Add tests for the command line functions too.
*/
@SmallTest
public class ShortcutManagerTest extends InstrumentationTestCase {
@@ -122,6 +130,8 @@
*/
private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
+ private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
// public for mockito
public class BaseContext extends MockContext {
@Override
@@ -260,8 +270,7 @@
@Override
boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
- // Sort of hack; do a simpler check.
- return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage);
+ return mDefaultLauncherChecker.test(callingPackage, userId);
}
@Override
@@ -279,12 +288,7 @@
@Override
void postToHandler(Runnable r) {
final long token = mContext.injectClearCallingIdentity();
- super.postToHandler(r);
- try {
- runTestOnUiThread(() -> {});
- } catch (Throwable e) {
- fail("runTestOnUiThread failed: " + e);
- }
+ r.run();
mContext.injectRestoreCallingIdentity(token);
}
@@ -316,36 +320,11 @@
}
@Override
- public void ensureInUserProfiles(UserHandle userToCheck, String message) {
- if (getCallingUserId() == userToCheck.getIdentifier()) {
- return; // okay
- }
- if (getCallingUserId() == USER_0 && userToCheck.getIdentifier() == USER_P0) {
- return; // profile, okay.
- }
- if (getCallingUserId() == USER_P0 && userToCheck.getIdentifier() == USER_0) {
- return; // profile, okay.
- }
-
- if (mInjectedCallingUid != Process.SYSTEM_UID) {
- throw new SecurityException("To access other users, you need to be SYSTEM" +
- ", but current UID=" + mInjectedCallingUid);
- }
- }
-
- @Override
public void verifyCallingPackage(String callingPackage) {
// SKIP
}
@Override
- boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, String debugMsg) {
- // This requires CROSS_USER
- assertEquals(Process.SYSTEM_UID, mInjectedCallingUid);
- return user.getIdentifier() == listeningUser.getIdentifier();
- }
-
- @Override
void postToPackageMonitorHandler(Runnable r) {
final long token = mContext.injectClearCallingIdentity();
r.run();
@@ -356,6 +335,18 @@
int injectBinderCallingUid() {
return mInjectedCallingUid;
}
+
+ @Override
+ long injectClearCallingIdentity() {
+ final int prevCallingUid = mInjectedCallingUid;
+ mInjectedCallingUid = Process.SYSTEM_UID;
+ return prevCallingUid;
+ }
+
+ @Override
+ void injectRestoreCallingIdentity(long token) {
+ mInjectedCallingUid = (int) token;
+ }
}
private class LauncherAppsTestable extends LauncherApps {
@@ -381,7 +372,11 @@
private ShortcutServiceInternal mInternal;
private LauncherAppImplTestable mLauncherAppImpl;
- private LauncherAppsTestable mLauncherApps;
+
+ // LauncherApps has per-instace state, so we need a differnt instance for each launcher.
+ private final Map<Pair<Integer, String>, LauncherAppsTestable>
+ mLauncherAppsMap = new HashMap<>();
+ private LauncherAppsTestable mLauncherApps; // Current one
private File mInjectedFilePathRoot;
@@ -394,7 +389,7 @@
private Map<String, PackageInfo> mInjectedPackages;
- private ArrayList<PackageWithUser> mUninstalledPackages;
+ private Set<PackageWithUser> mUninstalledPackages;
private PackageManager mMockPackageManager;
private PackageManagerInternal mMockPackageManagerInternal;
@@ -409,12 +404,21 @@
private static final String CALLING_PACKAGE_3 = "com.android.test.3";
private static final int CALLING_UID_3 = 10003;
+ private static final String CALLING_PACKAGE_4 = "com.android.test.4";
+ private static final int CALLING_UID_4 = 10004;
+
private static final String LAUNCHER_1 = "com.android.launcher.1";
private static final int LAUNCHER_UID_1 = 10011;
private static final String LAUNCHER_2 = "com.android.launcher.2";
private static final int LAUNCHER_UID_2 = 10012;
+ private static final String LAUNCHER_3 = "com.android.launcher.3";
+ private static final int LAUNCHER_UID_3 = 10013;
+
+ private static final String LAUNCHER_4 = "com.android.launcher.4";
+ private static final int LAUNCHER_UID_4 = 10014;
+
private static final int USER_0 = UserHandle.USER_SYSTEM;
private static final int USER_10 = 10;
private static final int USER_11 = 11;
@@ -425,6 +429,24 @@
private static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11);
private static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0);
+ private static final UserInfo USER_INFO_0 = withProfileGroupId(
+ new UserInfo(USER_0, "user0",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10);
+
+ private static final UserInfo USER_INFO_10 =
+ new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED);
+
+ private static final UserInfo USER_INFO_11 =
+ new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED);
+
+ private static final UserInfo USER_INFO_P0 = withProfileGroupId(
+ new UserInfo(USER_P0, "userP0",
+ UserInfo.FLAG_MANAGED_PROFILE), 10);
+
+ private BiPredicate<String, Integer> mDefaultLauncherChecker =
+ (callingPackage, userId) ->
+ LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
+ || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
private static final long START_TIME = 1440000000101L;
@@ -438,6 +460,13 @@
private static final int MAX_ICON_DIMENSION_LOWRAM = 32;
+ private static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
+
+ static {
+ QUERY_ALL.setQueryFlags(
+ ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -457,30 +486,83 @@
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
+ addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10);
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
+ addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6);
+ addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10);
- mUninstalledPackages = new ArrayList<>();
+ // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target.
+ updatePackageInfo(CALLING_PACKAGE_3,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+ updatePackageInfo(LAUNCHER_3,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ mUninstalledPackages = new HashSet<>();
mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
- // Empty the data directory.
- if (mInjectedFilePathRoot.exists()) {
- Assert.assertTrue("failed to delete dir",
- FileUtils.deleteContents(mInjectedFilePathRoot));
- }
- mInjectedFilePathRoot.mkdirs();
+ deleteAllSavedFiles();
+
+ // Set up users.
+ doAnswer(inv -> {
+ assertSystem();
+ return USER_INFO_0;
+ }).when(mMockUserManager).getUserInfo(eq(USER_0));
+
+ doAnswer(inv -> {
+ assertSystem();
+ return USER_INFO_10;
+ }).when(mMockUserManager).getUserInfo(eq(USER_10));
+
+ doAnswer(inv -> {
+ assertSystem();
+ return USER_INFO_11;
+ }).when(mMockUserManager).getUserInfo(eq(USER_11));
+
+ doAnswer(inv -> {
+ assertSystem();
+ return USER_INFO_P0;
+ }).when(mMockUserManager).getUserInfo(eq(USER_P0));
+
+ // User 0 is always running.
+ when(mMockUserManager.isUserRunning(eq(USER_0))).thenReturn(true);
initService();
setCaller(CALLING_PACKAGE_1);
}
+ private static UserInfo withProfileGroupId(UserInfo in, int groupId) {
+ in.profileGroupId = groupId;
+ return in;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (DUMP_IN_TEARDOWN) dumpsysOnLogcat("Teardown");
+
+ shutdownServices();
+
+ super.tearDown();
+ }
+
private Context getTestContext() {
return getInstrumentation().getContext();
}
+ private void deleteAllSavedFiles() {
+ // Empty the data directory.
+ if (mInjectedFilePathRoot.exists()) {
+ Assert.assertTrue("failed to delete dir",
+ FileUtils.deleteContents(mInjectedFilePathRoot));
+ }
+ mInjectedFilePathRoot.mkdirs();
+ }
+
/** (Re-) init the manager and the service. */
private void initService() {
+ shutdownServices();
+
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
// Instantiate targets.
@@ -490,12 +572,28 @@
mInternal = LocalServices.getService(ShortcutServiceInternal.class);
mLauncherAppImpl = new LauncherAppImplTestable(mServiceContext);
- mLauncherApps = new LauncherAppsTestable(mClientContext, mLauncherAppImpl);
+ mLauncherApps = null;
+ mLauncherAppsMap.clear();
// Load the setting file.
mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY);
}
+ private void shutdownServices() {
+ if (mService != null) {
+ // Flush all the unsaved data from the previous instance.
+ mService.saveDirtyInfo();
+ }
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+
+ mService = null;
+ mManager = null;
+ mInternal = null;
+ mLauncherAppImpl = null;
+ mLauncherApps = null;
+ mLauncherAppsMap.clear();
+ }
+
private void addPackage(String packageName, int uid, int version) {
addPackage(packageName, uid, version, packageName);
}
@@ -504,6 +602,10 @@
return Arrays.asList(array);
}
+ private <T> Set<T> set(Set<T> in) {
+ return new ArraySet<T>(in);
+ }
+
private Signature[] genSignatures(String... signatures) {
final Signature[] sigs = new Signature[signatures.length];
for (int i = 0; i < signatures.length; i++){
@@ -529,10 +631,24 @@
mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
}
+ private void updatePackageInfo(String packageName, Consumer<PackageInfo> c) {
+ c.accept(mInjectedPackages.get(packageName));
+ }
+
private void uninstallPackage(int userId, String packageName) {
+ if (ENABLE_DUMP) {
+ Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
+ }
mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
}
+ private void installPackage(int userId, String packageName) {
+ if (ENABLE_DUMP) {
+ Log.i(TAG, "Install package " + packageName + " / " + userId);
+ }
+ mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+ }
+
PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
final PackageInfo pi = mInjectedPackages.get(packageName);
@@ -560,6 +676,13 @@
mInjectedCallingUid =
Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false),
"Unknown package").applicationInfo.uid;
+
+ // Set up LauncherApps for this caller.
+ final Pair<Integer, String> key = Pair.create(userId, packageName);
+ if (!mLauncherAppsMap.containsKey(key)) {
+ mLauncherAppsMap.put(key, new LauncherAppsTestable(mClientContext, mLauncherAppImpl));
+ }
+ mLauncherApps = mLauncherAppsMap.get(key);
}
private void setCaller(String packageName) {
@@ -570,6 +693,10 @@
return mInjectedClientPackage;
}
+ private void setDefaultLauncherChecker(BiPredicate<String, Integer> p) {
+ mDefaultLauncherChecker = p;
+ }
+
private void runWithCaller(String packageName, int userId, Runnable r) {
final String previousPackage = mInjectedClientPackage;
final int previousUserId = UserHandle.getUserId(mInjectedCallingUid);
@@ -591,14 +718,22 @@
/** For debugging */
private void dumpsysOnLogcat() {
- if (!ENABLE_DUMP) return;
+ dumpsysOnLogcat("");
+ }
+
+ private void dumpsysOnLogcat(String message) {
+ dumpsysOnLogcat(message, false);
+ }
+
+ private void dumpsysOnLogcat(String message, boolean force) {
+ if (force || !ENABLE_DUMP) return;
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final PrintWriter pw = new PrintWriter(out);
mService.dumpInner(pw);
pw.close();
- Log.e(TAG, "Dumping ShortcutService:");
+ Log.e(TAG, "Dumping ShortcutService: " + message);
for (String line : out.toString().split("\n")) {
Log.e(TAG, line);
}
@@ -608,9 +743,13 @@
* For debugging, dump arbitrary file on logcat.
*/
private void dumpFileOnLogcat(String path) {
+ dumpFileOnLogcat(path, "");
+ }
+
+ private void dumpFileOnLogcat(String path, String message) {
if (!ENABLE_DUMP) return;
- Log.i(TAG, "Dumping file: " + path);
+ Log.i(TAG, "Dumping file: " + path + " " + message);
final StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
@@ -636,10 +775,14 @@
* For debugging, dump per-user state file on logcat.
*/
private void dumpUserFile(int userId) {
+ dumpUserFile(userId, "");
+ }
+
+ private void dumpUserFile(int userId, String message) {
mService.saveDirtyInfo();
dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+ "/user-" + userId
- + "/" + ShortcutService.FILENAME_USER_PACKAGES);
+ + "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
}
private void waitOnMainThread() throws Throwable {
@@ -778,6 +921,12 @@
return ret;
}
+ private static void resetAll(Collection<?> mocks) {
+ for (Object o : mocks) {
+ reset(o);
+ }
+ }
+
@NonNull
private ShortcutInfo findById(List<ShortcutInfo> list, String id) {
for (ShortcutInfo s : list) {
@@ -789,6 +938,10 @@
return null;
}
+ private void assertSystem() {
+ assertEquals("Caller must be system", Process.SYSTEM_UID, mInjectedCallingUid);
+ }
+
private void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) {
assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked());
assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked());
@@ -877,7 +1030,7 @@
private List<ShortcutInfo> assertAllHaveIcon(
@NonNull List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.hasIconFile() || s.hasIconResource());
+ assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource());
}
return actualShortcuts;
}
@@ -886,7 +1039,8 @@
private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts,
int shortcutFlags) {
for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.hasFlags(shortcutFlags));
+ assertTrue("ID " + s.getId() + " doesn't have flags " + shortcutFlags,
+ s.hasFlags(shortcutFlags));
}
return actualShortcuts;
}
@@ -1055,11 +1209,13 @@
return i;
}
- /**
- * Wrap a set in an ArraySet just to get a better toString.
- */
- private <T> Set<T> set(Set<T> in) {
- return new ArraySet<T>(in);
+ private ShortcutInfo parceled(ShortcutInfo si) {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(si, 0);
+ p.setDataPosition(0);
+ ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
+ p.recycle();
+ return si2;
}
/**
@@ -1320,8 +1476,6 @@
// Still 2 calls left.
assertEquals(2, mManager.getRemainingCallCount());
-
- // TODO Make sure pinned shortcuts won't be deleted.
}
public void testDeleteAllDynamicShortcuts() {
@@ -1351,8 +1505,6 @@
// Still 1 call left
assertEquals(1, mManager.getRemainingCallCount());
-
- // TODO Make sure pinned shortcuts won't be deleted.
}
public void testThrottling() {
@@ -1877,9 +2029,6 @@
});
}
- // TODO: updateShortcuts()
- // TODO: getPinnedShortcuts()
-
// === Test for launcher side APIs ===
private static ShortcutQuery buildQuery(long changedSince,
@@ -1893,6 +2042,20 @@
return q;
}
+ private static ShortcutQuery buildAllQuery(String packageName) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+ return q;
+ }
+
+ private static ShortcutQuery buildPinnedQuery(String packageName) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED);
+ return q;
+ }
+
public void testGetShortcuts() {
// Set up shortcuts.
@@ -2928,12 +3091,6 @@
}
public void testLauncherCallback() throws Throwable {
-
- // TODO Add "multi" version -- run the test with two launchers and make sure the callback
- // argument only contains the ones that are actually visible to each launcher.
-
- when(mMockUserManager.isUserRunning(eq(USER_0))).thenReturn(true);
-
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
// Set listeners
@@ -2989,6 +3146,7 @@
// Test for addDynamicShortcut.
reset(c0);
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+ dumpsysOnLogcat("before addDynamicShortcut");
assertTrue(mManager.addDynamicShortcut(makeShortcut("s4")));
});
@@ -3052,6 +3210,7 @@
// Remove CALLING_PACKAGE_2
reset(c0);
+ uninstallPackage(USER_0, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
// Should get a callback with an empty list.
@@ -3065,6 +3224,156 @@
assertEquals(0, shortcuts.getValue().size());
}
+ private void assertCallbackNotReceived(LauncherApps.Callback mock) {
+ verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(),
+ any(UserHandle.class));
+ }
+
+ private void assertCallbackReceived(LauncherApps.Callback mock,
+ UserHandle user, String packageName, String... ids) {
+ ArgumentCaptor<List> shortcutsCaptor = ArgumentCaptor.forClass(List.class);
+
+ verify(mock, times(1)).onShortcutsChanged(eq(packageName), shortcutsCaptor.capture(),
+ eq(user));
+ assertShortcutIds(shortcutsCaptor.getValue(), ids);
+ }
+
+ public void testLauncherCallback_crossProfile() throws Throwable {
+ prepareCrossProfileDataSet();
+
+ final Handler h = new Handler(Looper.getMainLooper());
+
+ final LauncherApps.Callback c0_1 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c0_2 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c0_3 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c0_4 = mock(LauncherApps.Callback.class);
+
+ final LauncherApps.Callback cP0_1 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c10_1 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c10_2 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c11_1 = mock(LauncherApps.Callback.class);
+
+ final List<LauncherApps.Callback> all =
+ list(c0_1, c0_2, c0_3, c0_4, cP0_1, c10_1, c11_1);
+
+ setDefaultLauncherChecker((pkg, userId) -> {
+ switch (userId) {
+ case USER_0:
+ return LAUNCHER_2.equals(pkg);
+ case USER_P0:
+ return LAUNCHER_1.equals(pkg);
+ case USER_10:
+ return LAUNCHER_1.equals(pkg);
+ case USER_11:
+ return LAUNCHER_1.equals(pkg);
+ default:
+ return false;
+ }
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> mLauncherApps.registerCallback(c0_1, h));
+ runWithCaller(LAUNCHER_2, USER_0, () -> mLauncherApps.registerCallback(c0_2, h));
+ runWithCaller(LAUNCHER_3, USER_0, () -> mLauncherApps.registerCallback(c0_3, h));
+ runWithCaller(LAUNCHER_4, USER_0, () -> mLauncherApps.registerCallback(c0_4, h));
+ runWithCaller(LAUNCHER_1, USER_P0, () -> mLauncherApps.registerCallback(cP0_1, h));
+ runWithCaller(LAUNCHER_1, USER_10, () -> mLauncherApps.registerCallback(c10_1, h));
+ runWithCaller(LAUNCHER_2, USER_10, () -> mLauncherApps.registerCallback(c10_2, h));
+ runWithCaller(LAUNCHER_1, USER_11, () -> mLauncherApps.registerCallback(c11_1, h));
+
+ // User 0.
+
+ resetAll(all);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.deleteDynamicShortcut("x");
+ });
+ waitOnMainThread();
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_3);
+ assertCallbackNotReceived(c0_4);
+ assertCallbackNotReceived(c10_1);
+ assertCallbackNotReceived(c10_2);
+ assertCallbackNotReceived(c11_1);
+ assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3");
+ assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+
+ // User 0, different package.
+
+ resetAll(all);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ mManager.deleteDynamicShortcut("x");
+ });
+ waitOnMainThread();
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_3);
+ assertCallbackNotReceived(c0_4);
+ assertCallbackNotReceived(c10_1);
+ assertCallbackNotReceived(c10_2);
+ assertCallbackNotReceived(c11_1);
+ assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_3, "s1", "s2", "s3", "s4");
+ assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_3,
+ "s1", "s2", "s3", "s4", "s5", "s6");
+
+ // Work profile, but not running, so don't send notifications.
+
+ resetAll(all);
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ mManager.deleteDynamicShortcut("x");
+ });
+ waitOnMainThread();
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_2);
+ assertCallbackNotReceived(c0_3);
+ assertCallbackNotReceived(c0_4);
+ assertCallbackNotReceived(cP0_1);
+ assertCallbackNotReceived(c10_1);
+ assertCallbackNotReceived(c10_2);
+ assertCallbackNotReceived(c11_1);
+
+ // Work profile, now running.
+
+ when(mMockUserManager.isUserRunning(anyInt())).thenReturn(false);
+ when(mMockUserManager.isUserRunning(eq(USER_P0))).thenReturn(true);
+
+ resetAll(all);
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ mManager.deleteDynamicShortcut("x");
+ });
+ waitOnMainThread();
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_3);
+ assertCallbackNotReceived(c0_4);
+ assertCallbackNotReceived(c10_1);
+ assertCallbackNotReceived(c10_2);
+ assertCallbackNotReceived(c11_1);
+ assertCallbackReceived(c0_2, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s5");
+ assertCallbackReceived(cP0_1, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
+
+ // Normal secondary user.
+
+ when(mMockUserManager.isUserRunning(anyInt())).thenReturn(false);
+ when(mMockUserManager.isUserRunning(eq(USER_10))).thenReturn(true);
+
+ resetAll(all);
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ mManager.deleteDynamicShortcut("x");
+ });
+ waitOnMainThread();
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_2);
+ assertCallbackNotReceived(c0_3);
+ assertCallbackNotReceived(c0_4);
+ assertCallbackNotReceived(cP0_1);
+ assertCallbackNotReceived(c10_2);
+ assertCallbackNotReceived(c11_1);
+ assertCallbackReceived(c10_1, HANDLE_USER_10, CALLING_PACKAGE_1,
+ "x1", "x2", "x3", "x4", "x5");
+ }
+
// === Test for persisting ===
public void testSaveAndLoadUser_empty() {
@@ -3299,9 +3608,9 @@
// Check the registered packages.
dumpsysOnLogcat();
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3326,13 +3635,14 @@
mService.saveDirtyInfo();
// Nonexistent package.
+ uninstallPackage(USER_0, "abc");
mService.cleanUpPackageLocked("abc", USER_0, USER_0);
// No changes.
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3357,12 +3667,13 @@
mService.saveDirtyInfo();
// Remove a package.
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3387,12 +3698,13 @@
mService.saveDirtyInfo();
// Remove a launcher.
+ uninstallPackage(USER_10, LAUNCHER_1);
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3414,12 +3726,13 @@
mService.saveDirtyInfo();
// Remove a package.
+ uninstallPackage(USER_10, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3441,12 +3754,13 @@
mService.saveDirtyInfo();
// Remove the other launcher from user 10 too.
+ uninstallPackage(USER_10, LAUNCHER_2);
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3468,12 +3782,13 @@
mService.saveDirtyInfo();
// More remove.
+ uninstallPackage(USER_10, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3494,97 +3809,6 @@
mService.saveDirtyInfo();
}
-
- public void testSaveAndLoadUser_forBackup() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Pin some.
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), HANDLE_USER_10);
- });
-
- // Check the state.
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Make sure all the information is persisted.
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
- }
-
public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3841,13 +4065,9 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
}
- // TODO Detailed test for hasShortcutPermissionInner().
-
- // TODO Add tests for the command line functions too.
-
private void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
int version, String... signatures) {
- assertEquals(expected, spi.canRestoreTo(genPackage(
+ assertEquals(expected, spi.canRestoreTo(mService, genPackage(
"dummy", /* uid */ 0, version, signatures)));
}
@@ -3919,6 +4139,7 @@
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
@@ -3929,6 +4150,7 @@
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+ uninstallPackage(USER_10, CALLING_PACKAGE_2);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
@@ -3961,7 +4183,1193 @@
assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
}
- public void testHandlePackageUpdate() {
- // TODO: Make sure unshadow is called.
+ private void backupAndRestore() {
+ int prevUid = mInjectedCallingUid;
+
+ mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it.
+
+ dumpsysOnLogcat("Before backup");
+
+ final byte[] payload = mService.getBackupPayload(USER_0);
+ if (ENABLE_DUMP) {
+ final String xml = new String(payload);
+ Log.i(TAG, "Backup payload:");
+ for (String line : xml.split("\n")) {
+ Log.i(TAG, line);
+ }
+ }
+
+ // Before doing anything else, uninstall all packages.
+ for (int userId : list(USER_0, USER_P0)) {
+ for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+ LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) {
+ uninstallPackage(userId, pkg);
+ }
+ }
+
+ shutdownServices();
+
+ deleteAllSavedFiles();
+
+ initService();
+ mService.applyRestore(payload, USER_0);
+
+ // handleUnlockUser will perform the gone package check, but it shouldn't remove
+ // shadow information.
+ mService.handleUnlockUser(USER_0);
+
+ dumpsysOnLogcat("After restore");
+
+ mInjectedCallingUid = prevUid;
+ }
+
+ private void prepareCrossProfileDataSet() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list()));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"),
+ makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6"))));
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_3, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_4, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0);
+ });
+
+ // Launcher on a managed profile is referring ot user 0!
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"),
+ HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"),
+ HANDLE_USER_10);
+ });
+
+ // Then remove some dynamic shortcuts.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list()));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"))));
+ });
+ }
+
+ private void prepareForBackupTest() {
+
+ prepareCrossProfileDataSet();
+
+ backupAndRestore();
+ }
+
+ private void assertExistsAndShadow(ShortcutPackageItem spi) {
+ assertNotNull(spi);
+ assertTrue(spi.getPackageInfo().isShadow());
+ }
+
+ /**
+ * Make sure the backup data doesn't have the following information:
+ * - Launchers on other users.
+ * - Non-backup app information.
+ *
+ * But restores all other infomation.
+ *
+ * It also omits the following pieces of information, but that's tested in
+ * {@link #testShortcutInfoSaveAndLoad_forBackup}.
+ * - Unpinned dynamic shortcuts
+ * - Bitmaps
+ */
+ public void testBackupAndRestore() {
+ prepareForBackupTest();
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_backupRestoreTwice() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ dumpsysOnLogcat("Before second backup");
+
+ backupAndRestore();
+
+ dumpsysOnLogcat("After second backup");
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_backupRestoreMultiple() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ // This also shouldn't affect the result.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+
+ backupAndRestore();
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_restoreToNewVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_restoreToSuperSetSignatures() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ // Change package signatures.
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
+
+ checkBackupAndRestore_success();
+ }
+
+ private void checkBackupAndRestore_success() {
+ // Make sure non-system user is not restored.
+ final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
+ assertEquals(0, userP0.getAllPackages().size());
+ assertEquals(0, userP0.getAllLaunchers().size());
+
+ // Make sure only "allowBackup" apps are restored, and are shadow.
+ final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
+ assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1));
+ assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2));
+ assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
+ assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
+
+ assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3));
+ assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
+ assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty, not restored */ );
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty, not restored */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty, not restored */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ // 3 shouldn't be backed up, so no pinned shortcuts.
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ // Launcher on a different profile shouldn't be restored.
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ assertEquals(0,
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+ .size());
+ assertEquals(0,
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+ .size());
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+ });
+
+ // Package on a different profile, no restore.
+ installPackage(USER_P0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ // Restore launcher 2 on user 0.
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+
+ // Restoration of launcher2 shouldn't affect other packages; so do the same checks and
+ // make sure they still have the same result.
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+ }
+
+ public void testBackupAndRestore_publisherLowerVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ public void testBackupAndRestore_publisherWrongSignature() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ public void testBackupAndRestore_publisherNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(CALLING_PACKAGE_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ private void checkBackupAndRestore_publisherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testBackupAndRestore_launcherLowerVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ public void testBackupAndRestore_launcherWrongSignature() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ public void testBackupAndRestore_launcherNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(LAUNCHER_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ private void checkBackupAndRestore_launcherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+
+ // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here.
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ // Now we try to restore launcher 1. Then we realize it's not restorable, so L1 has no pinned
+ // shortcuts.
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+
+ // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned.
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(CALLING_PACKAGE_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ updatePackageInfo(LAUNCHER_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_publisherAndLauncherNotRestored();
+ }
+
+ private void checkBackupAndRestore_publisherAndLauncherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ // Because launcher 1 wasn't restored, "s1" is no longer pinned.
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s2", "s3");
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testSaveAndLoad_crossProfile() {
+ prepareCrossProfileDataSet();
+
+ dumpsysOnLogcat("Before save & load");
+
+ mService.saveDirtyInfo();
+ initService();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4");
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5");
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ });
+ runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+ /* empty */);
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+ /* empty */);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+ /* empty */);
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+ /* empty */);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "x1", "x2", "x3");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "x4", "x5");
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s1");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s1", "s2");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s1", "s2", "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s1", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ TestUtils.assertExpectException(
+ SecurityException.class, "", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+ });
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s2");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s2", "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s2", "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s2", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_3, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s3", "s4", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s3", "s6");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_4, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s3", "s4", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s3", "s4", "s5", "s6");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s1", "s4");
+ TestUtils.assertExpectException(
+ SecurityException.class, "unrelated profile", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+ });
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10),
+ "x4", "x5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10)
+ /* empty */);
+ TestUtils.assertExpectException(
+ SecurityException.class, "unrelated profile", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0);
+ });
+ TestUtils.assertExpectException(
+ SecurityException.class, "unrelated profile", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
+ });
+ });
+ }
+
+ // ShortcutInfo tests
+
+ public void testShortcutInfoMissingMandatoryFields() {
+ TestUtils.assertExpectException(
+ IllegalArgumentException.class,
+ "ID must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).build());
+ TestUtils.assertExpectException(
+ IllegalArgumentException.class,
+ "title must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
+ .enforceMandatoryFields());
+ TestUtils.assertExpectException(
+ NullPointerException.class,
+ "Intent must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build()
+ .enforceMandatoryFields());
+ }
+
+ public void testShortcutInfoParcel() {
+ ShortcutInfo si = parceled(new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setTitle("title")
+ .setIntent(makeIntent("action", ShortcutActivity.class))
+ .build());
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals("title", si.getTitle());
+ assertEquals("action", si.getIntent().getAction());
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+
+ si = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ si.setBitmapPath("abc");
+ si.setIconResourceId(456);
+
+ si = parceled(si);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoClone() {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(null, si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals(null, si.getTitle());
+ assertEquals(null, si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(0, si.getWeight());
+ assertEquals(null, si.getExtras());
+
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si;
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setActivityComponent(new ComponentName("x", "y")).build());
+ assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+ assertEquals("content://x.y.z/", si.getIcon().getUriString());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitle("xyz").build());
+ assertEquals("xyz", si.getTitle());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setText("xxx").build());
+ assertEquals("xxx", si.getText());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
+ assertEquals("action2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
+ assertEquals("action3", si.getIntent().getAction());
+ assertEquals("x", si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setWeight(999).build());
+ assertEquals(999, si.getWeight());
+
+
+ PersistableBundle pb2 = new PersistableBundle();
+ pb2.putInt("x", 99);
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setExtras(pb2).build());
+ assertEquals(99, si.getExtras().getInt("x"));
+
+ final long timestamp = si.getLastChangedTimestamp();
+ Thread.sleep(2);
+
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitle("xyz").build());
+
+ assertTrue(si.getLastChangedTimestamp() > timestamp);
+ }
+
+ public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(bmp32x32)
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+
+ mManager.addDynamicShortcut(sorig);
+
+ Thread.sleep(2);
+ final long now = System.currentTimeMillis();
+
+ // Save and load.
+ mService.saveDirtyInfo();
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+ assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags());
+ assertNotNull(si.getBitmapPath()); // Something should be set.
+ assertEquals(0, si.getIconResourceId());
+ assertTrue(si.getLastChangedTimestamp() < now);
+ }
+
+ public void testShortcutInfoSaveAndLoad_forBackup() {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(bmp32x32)
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+
+ mManager.addDynamicShortcut(sorig);
+
+ // Dynamic shortcuts won't be backed up, so we need to pin it.
+ setCaller(LAUNCHER_1, USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
+
+ // Do backup & restore.
+ backupAndRestore();
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+ assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertNull(si.getBitmapPath()); // No icon.
+ assertEquals(0, si.getIconResourceId());
+ }
+
+ public void testDumpsys_crossProfile() {
+ prepareCrossProfileDataSet();
+ dumpsysOnLogcat("test1", /* force= */ true);
+ }
+
+ public void testDumpsys_withIcons() {
+ testIcons();
+ // Dump after having some icons.
+ dumpsysOnLogcat("test1", /* force= */ true);
}
}
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index ed7351f8..033312b 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -62,7 +62,7 @@
private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
private static final String WEARABLE_ACTION_GOOGLE =
"com.google.android.wearable.action.GOOGLE";
- private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
+ private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 60000; //60s to allow app to idle
private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
index ad02d2b..c0583ce 100644
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
+++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
@@ -18,6 +18,7 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -40,6 +41,7 @@
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Random;
import java.util.UUID;
@@ -53,7 +55,7 @@
static final int MSG_GENERIC_TRIGGER = 4;
private Random random = new Random();
- private ArrayList<UUID> loadedModelUuids;
+ private HashSet<UUID> loadedModelUuids;
private ISoundTriggerService soundTriggerService;
private SoundTriggerManager soundTriggerManager;
@@ -68,7 +70,7 @@
soundTriggerManager = (SoundTriggerManager) context.getSystemService(
Context.SOUND_TRIGGER_SERVICE);
- loadedModelUuids = new ArrayList<UUID>();
+ loadedModelUuids = new HashSet<UUID>();
}
@Override
@@ -170,6 +172,101 @@
verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
}
+ /**
+ * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping
+ * recognition. Intended to find unexpected errors that occur in unexpected states.
+ */
+ @LargeTest
+ public void testFuzzGenericSoundModel() throws Exception {
+ int numModels = 2;
+
+ final int STATUS_UNLOADED = 0;
+ final int STATUS_LOADED = 1;
+ final int STATUS_STARTED = 2;
+
+ class ModelInfo {
+ int status;
+ GenericSoundModel model;
+
+ public ModelInfo(GenericSoundModel model, int status) {
+ this.status = status;
+ this.model = model;
+ }
+ }
+
+ Random predictableRandom = new Random(100);
+
+ ArrayList modelInfos = new ArrayList<ModelInfo>();
+ for(int i=0; i<numModels; i++) {
+ // Create sound model
+ byte[] data = new byte[1024];
+ predictableRandom.nextBytes(data);
+ UUID modelUuid = UUID.randomUUID();
+ UUID mVendorUuid = UUID.randomUUID();
+ GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
+ ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED);
+ modelInfos.add(modelInfo);
+ }
+
+ boolean captureTriggerAudio = true;
+ boolean allowMultipleTriggers = true;
+ RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
+ null, null);
+ TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
+
+
+ int numOperationsToRun = 100;
+ for(int i=0; i<numOperationsToRun; i++) {
+ // Select a random model
+ int modelInfoIndex = predictableRandom.nextInt(modelInfos.size());
+ ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex);
+
+ // Perform a random operation
+ int operation = predictableRandom.nextInt(5);
+
+ if (operation == 0 && modelInfo.status == STATUS_UNLOADED) {
+ // Update and start sound model
+ soundTriggerService.updateSoundModel(modelInfo.model);
+ loadedModelUuids.add(modelInfo.model.uuid);
+ modelInfo.status = STATUS_LOADED;
+ } else if (operation == 1 && modelInfo.status == STATUS_LOADED) {
+ // Start the sound model
+ int r = soundTriggerService.startRecognition(new ParcelUuid(modelInfo.model.uuid),
+ spyCallback, config);
+ assertEquals("Could Not Start Recognition with code: " + r,
+ android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
+ modelInfo.status = STATUS_STARTED;
+ } else if (operation == 2 && modelInfo.status == STATUS_STARTED) {
+ // Send trigger to stub HAL
+ Socket socket = new Socket(InetAddress.getLocalHost(), 14035);
+ DataOutputStream out = new DataOutputStream(socket.getOutputStream());
+ out.writeBytes("trig " + modelInfo.model.uuid + "\r\n");
+ out.flush();
+ socket.close();
+
+ // Verify trigger was received
+ verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
+ reset(spyCallback);
+ } else if (operation == 3 && modelInfo.status == STATUS_STARTED) {
+ // Stop recognition
+ int r = soundTriggerService.stopRecognition(new ParcelUuid(modelInfo.model.uuid),
+ spyCallback);
+ assertEquals("Could Not Stop Recognition with code: " + r,
+ android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
+ modelInfo.status = STATUS_LOADED;
+ } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) {
+ // Delete sound model
+ soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.uuid));
+ loadedModelUuids.remove(modelInfo.model.uuid);
+
+ // Confirm it was deleted
+ GenericSoundModel returnedModel =
+ soundTriggerService.getSoundModel(new ParcelUuid(modelInfo.model.uuid));
+ assertEquals(null, returnedModel);
+ modelInfo.status = STATUS_UNLOADED;
+ }
+ }
+ }
public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub {
@Override
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 641c34b..d631f35 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,7 +33,7 @@
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv", ".webm"
+ ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"
};
/* fwd decls, so I can write this downward */
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index f5938cf..265ebd1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -423,21 +423,13 @@
}
@LayoutlibDelegate
- /*package*/ static void native_offset(long nPath, float dx, float dy, long dst_path) {
+ /*package*/ static void native_offset(long nPath, float dx, float dy) {
Path_Delegate pathDelegate = sManager.getDelegate(nPath);
if (pathDelegate == null) {
return;
}
- // could be null if the int is 0;
- Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
-
- pathDelegate.offset(dx, dy, dstDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_offset(long nPath, float dx, float dy) {
- native_offset(nPath, dx, dy, 0);
+ pathDelegate.offset(dx, dy);
}
@LayoutlibDelegate
@@ -860,21 +852,14 @@
*
* @param dx The amount in the X direction to offset the entire path
* @param dy The amount in the Y direction to offset the entire path
- * @param dst The translated path is written here. If this is null, then
- * the original path is modified.
*/
- public void offset(float dx, float dy, Path_Delegate dst) {
+ public void offset(float dx, float dy) {
GeneralPath newPath = new GeneralPath();
PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
newPath.append(iterator, false /*connect*/);
-
- if (dst != null) {
- dst.mPath = newPath;
- } else {
- mPath = newPath;
- }
+ mPath = newPath;
}
/**
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 7faee1b..f9e008e 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -403,8 +403,15 @@
}
@Override
- public void setNewConfiguration(Configuration arg0) throws RemoteException {
+ public int[] setNewConfiguration(Configuration arg0) throws RemoteException {
// TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
index 1465f50..24f7887 100644
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -55,7 +55,7 @@
private String mName;
@LayoutlibDelegate
- /*package*/ static long nCreate(String name) {
+ /*package*/ static long nCreate(RenderNode thisRenderNode, String name) {
RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
renderNodeDelegate.mName = name;
return sManager.addNewDelegate(renderNodeDelegate);