Merge "Persist the debug time correction flag."
diff --git a/Android.mk b/Android.mk
index f544620..f318a5a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -265,6 +265,7 @@
core/java/android/security/IKeystoreService.aidl \
core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \
core/java/android/service/autofill/IAutoFillService.aidl \
+ core/java/android/service/autofill/IAutoFillServiceConnection.aidl \
core/java/android/service/autofill/IFillCallback.aidl \
core/java/android/service/autofill/ISaveCallback.aidl \
core/java/android/service/carrier/ICarrierService.aidl \
diff --git a/api/current.txt b/api/current.txt
index c7074cc..693d512 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -298,6 +298,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -1005,7 +1006,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
- field public static final int primaryContentAlpha = 16843367; // 0x1010267
+ field public static final int primaryContentAlpha = 16844117; // 0x1010555
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -1061,7 +1062,9 @@
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
+ field public static final int requiredFeature = 16844119; // 0x1010557
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
+ field public static final int requiredNotFeature = 16844120; // 0x1010558
field public static final int requiresFadingEdge = 16843685; // 0x10103a5
field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
field public static final int resizeClip = 16843983; // 0x10104cf
@@ -1130,7 +1133,7 @@
field public static final int searchSuggestSelection = 16843224; // 0x10101d8
field public static final int searchSuggestThreshold = 16843373; // 0x101026d
field public static final int searchViewStyle = 16843904; // 0x1010480
- field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
+ field public static final int secondaryContentAlpha = 16844118; // 0x1010556
field public static final int secondaryProgress = 16843064; // 0x1010138
field public static final int secondaryProgressTint = 16843879; // 0x1010467
field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -3909,8 +3912,9 @@
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR;
- field public static final int IMPORTANCE_BACKGROUND = 400; // 0x190
- field public static final int IMPORTANCE_EMPTY = 500; // 0x1f4
+ field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190
+ field public static final int IMPORTANCE_CACHED = 400; // 0x190
+ field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4
field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64
field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d
field public static final int IMPORTANCE_GONE = 1000; // 0x3e8
@@ -5482,6 +5486,7 @@
public final class NotificationChannel implements android.os.Parcelable {
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
+ ctor public NotificationChannel(java.lang.String, int, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
method public boolean canShowBadge();
@@ -5495,6 +5500,7 @@
method public int getLightColor();
method public int getLockscreenVisibility();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
@@ -5514,12 +5520,14 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -6549,6 +6557,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -6565,6 +6574,7 @@
method public java.lang.String getIdEntry();
method public java.lang.String getIdPackage();
method public java.lang.String getIdType();
+ method public int getInputType();
method public int getLeft();
method public int getScrollX();
method public int getScrollY();
@@ -8445,8 +8455,10 @@
field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+ field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8457,7 +8469,6 @@
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
- field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -10345,6 +10356,7 @@
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean hasSystemFeature(java.lang.String, int);
method public abstract boolean isInstantApp();
+ method public abstract boolean isInstantApp(java.lang.String);
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -10476,8 +10488,11 @@
field public static final int GET_SIGNATURES = 64; // 0x40
field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800
+ field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2
+ field public static final int INSTALL_REASON_DEVICE_SETUP = 3; // 0x3
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
+ field public static final int INSTALL_REASON_USER = 4; // 0x4
field public static final int MATCH_ALL = 131072; // 0x20000
field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000
@@ -13829,12 +13844,12 @@
}
public final class Icon implements android.os.Parcelable {
+ method public static android.graphics.drawable.Icon createWithAdaptiveBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithContentUri(java.lang.String);
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
- method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
method public int describeContents();
@@ -30125,12 +30140,12 @@
field public static final int BATTERY_PLUGGED_AC = 1; // 0x1
field public static final int BATTERY_PLUGGED_USB = 2; // 0x2
field public static final int BATTERY_PLUGGED_WIRELESS = 4; // 0x4
- field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_PROPERTY_CAPACITY = 4; // 0x4
field public static final int BATTERY_PROPERTY_CHARGE_COUNTER = 1; // 0x1
field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3
field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2
field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5
+ field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2
field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
@@ -36411,7 +36426,6 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
- field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -36463,6 +36477,7 @@
public abstract class AutoFillService extends android.app.Service {
ctor public AutoFillService();
+ method public final void disableSelf();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -36525,6 +36540,7 @@
method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
}
}
@@ -36922,6 +36938,7 @@
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final java.lang.String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
+ field public static final java.lang.String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
field public static final java.lang.String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
}
@@ -38231,6 +38248,7 @@
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -38800,6 +38818,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -38836,6 +38855,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -39523,6 +39543,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getLine1Number();
@@ -40371,6 +40392,7 @@
method public boolean hasSystemFeature(java.lang.String);
method public boolean hasSystemFeature(java.lang.String, int);
method public boolean isInstantApp();
+ method public boolean isInstantApp(java.lang.String);
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -44805,6 +44827,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45122,6 +45145,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -45263,6 +45287,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -45915,6 +45953,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -45932,6 +45971,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
@@ -47183,10 +47223,11 @@
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
+ method public boolean isDate();
method public boolean isList();
method public boolean isText();
method public boolean isToggle();
@@ -47196,9 +47237,11 @@
public final class AutoFillValue implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forDate(long);
method public static android.view.autofill.AutoFillValue forList(int);
method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public long getDateValue();
method public int getListValue();
method public java.lang.CharSequence getTextValue();
method public boolean getToggleValue();
diff --git a/api/system-current.txt b/api/system-current.txt
index a467351..f095936 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -207,7 +207,6 @@
field public static final java.lang.String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES";
field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
- field public static final java.lang.String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
@@ -411,6 +410,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -1118,7 +1118,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
- field public static final int primaryContentAlpha = 16843367; // 0x1010267
+ field public static final int primaryContentAlpha = 16844117; // 0x1010555
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -1174,7 +1174,9 @@
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
+ field public static final int requiredFeature = 16844119; // 0x1010557
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
+ field public static final int requiredNotFeature = 16844120; // 0x1010558
field public static final int requiresFadingEdge = 16843685; // 0x10103a5
field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
field public static final int resizeClip = 16843983; // 0x10104cf
@@ -1247,7 +1249,7 @@
field public static final int searchSuggestSelection = 16843224; // 0x10101d8
field public static final int searchSuggestThreshold = 16843373; // 0x101026d
field public static final int searchViewStyle = 16843904; // 0x1010480
- field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
+ field public static final int secondaryContentAlpha = 16844118; // 0x1010556
field public static final int secondaryProgress = 16843064; // 0x1010138
field public static final int secondaryProgressTint = 16843879; // 0x1010467
field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -4048,8 +4050,9 @@
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR;
- field public static final int IMPORTANCE_BACKGROUND = 400; // 0x190
- field public static final int IMPORTANCE_EMPTY = 500; // 0x1f4
+ field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190
+ field public static final int IMPORTANCE_CACHED = 400; // 0x190
+ field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4
field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64
field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d
field public static final int IMPORTANCE_GONE = 1000; // 0x3e8
@@ -5659,6 +5662,7 @@
public final class NotificationChannel implements android.os.Parcelable {
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
+ ctor public NotificationChannel(java.lang.String, int, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
method public boolean canShowBadge();
@@ -5672,6 +5676,7 @@
method public int getLightColor();
method public int getLockscreenVisibility();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public android.net.Uri getSound();
method public int getUserLockedFields();
method public long[] getVibrationPattern();
@@ -5708,6 +5713,7 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public void addChannel(android.app.NotificationChannel);
method public android.app.NotificationChannelGroup clone();
@@ -5715,6 +5721,7 @@
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public org.json.JSONObject toJson() throws org.json.JSONException;
method public void writeToParcel(android.os.Parcel, int);
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
@@ -6789,6 +6796,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -6805,6 +6813,7 @@
method public java.lang.String getIdEntry();
method public java.lang.String getIdPackage();
method public java.lang.String getIdType();
+ method public int getInputType();
method public int getLeft();
method public int getScrollX();
method public int getScrollY();
@@ -6942,26 +6951,64 @@
public class BackupManagerMonitor {
ctor public BackupManagerMonitor();
method public void onEvent(android.os.Bundle);
+ field public static final java.lang.String EXTRA_LOG_CANCEL_ALL = "android.app.backup.extra.LOG_CANCEL_ALL";
field public static final java.lang.String EXTRA_LOG_EVENT_CATEGORY = "android.app.backup.extra.LOG_EVENT_CATEGORY";
field public static final java.lang.String EXTRA_LOG_EVENT_ID = "android.app.backup.extra.LOG_EVENT_ID";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_NAME = "android.app.backup.extra.LOG_EVENT_PACKAGE_NAME";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_VERSION = "android.app.backup.extra.LOG_EVENT_PACKAGE_VERSION";
+ field public static final java.lang.String EXTRA_LOG_EXCEPTION_FULL_BACKUP = "android.app.backup.extra.LOG_EXCEPTION_FULL_BACKUP";
+ field public static final java.lang.String EXTRA_LOG_ILLEGAL_KEY = "android.app.backup.extra.LOG_ILLEGAL_KEY";
+ field public static final java.lang.String EXTRA_LOG_MANIFEST_PACKAGE_NAME = "android.app.backup.extra.LOG_MANIFEST_PACKAGE_NAME";
field public static final java.lang.String EXTRA_LOG_OLD_VERSION = "android.app.backup.extra.LOG_OLD_VERSION";
+ field public static final java.lang.String EXTRA_LOG_POLICY_ALLOW_APKS = "android.app.backup.extra.LOG_POLICY_ALLOW_APKS";
+ field public static final java.lang.String EXTRA_LOG_PREFLIGHT_ERROR = "android.app.backup.extra.LOG_PREFLIGHT_ERROR";
+ field public static final java.lang.String EXTRA_LOG_RESTORE_ANYWAY = "android.app.backup.extra.LOG_RESTORE_ANYWAY";
+ field public static final java.lang.String EXTRA_LOG_RESTORE_VERSION = "android.app.backup.extra.LOG_RESTORE_VERSION";
+ field public static final java.lang.String EXTRA_LOG_WIDGET_PACKAGE_NAME = "android.app.backup.extra.LOG_WIDGET_PACKAGE_NAME";
field public static final int LOG_EVENT_CATEGORY_AGENT = 2; // 0x2
field public static final int LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY = 3; // 0x3
field public static final int LOG_EVENT_CATEGORY_TRANSPORT = 1; // 0x1
+ field public static final int LOG_EVENT_ID_APK_NOT_INSTALLED = 40; // 0x28
field public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28; // 0x1c
+ field public static final int LOG_EVENT_ID_BACKUP_DISABLED = 13; // 0xd
+ field public static final int LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK = 41; // 0x29
field public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30; // 0x1e
- field public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4; // 0x4
+ field public static final int LOG_EVENT_ID_CORRUPT_MANIFEST = 46; // 0x2e
+ field public static final int LOG_EVENT_ID_DEVICE_NOT_PROVISIONED = 14; // 0xe
+ field public static final int LOG_EVENT_ID_ERROR_PREFLIGHT = 16; // 0x10
+ field public static final int LOG_EVENT_ID_EXCEPTION_FULL_BACKUP = 19; // 0x13
+ field public static final int LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE = 43; // 0x2b
+ field public static final int LOG_EVENT_ID_FULL_BACKUP_CANCEL = 4; // 0x4
+ field public static final int LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE = 39; // 0x27
+ field public static final int LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH = 37; // 0x25
field public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45; // 0x2d
- field public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21; // 0x15
+ field public static final int LOG_EVENT_ID_ILLEGAL_KEY = 5; // 0x5
+ field public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL = 21; // 0x15
field public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31; // 0x1f
+ field public static final int LOG_EVENT_ID_LOST_TRANSPORT = 25; // 0x19
+ field public static final int LOG_EVENT_ID_MISSING_SIGNATURE = 42; // 0x2a
+ field public static final int LOG_EVENT_ID_NO_DATA_TO_SEND = 7; // 0x7
field public static final int LOG_EVENT_ID_NO_PACKAGES = 49; // 0x31
+ field public static final int LOG_EVENT_ID_NO_PM_METADATA_RECEIVED = 23; // 0x17
field public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22; // 0x16
+ field public static final int LOG_EVENT_ID_PACKAGE_INELIGIBLE = 9; // 0x9
+ field public static final int LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT = 10; // 0xa
field public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12; // 0xc
field public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26; // 0x1a
+ field public static final int LOG_EVENT_ID_PACKAGE_STOPPED = 11; // 0xb
field public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15; // 0xf
+ field public static final int LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA = 24; // 0x18
+ field public static final int LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT = 18; // 0x12
+ field public static final int LOG_EVENT_ID_RESTORE_ANY_VERSION = 34; // 0x22
+ field public static final int LOG_EVENT_ID_RESTORE_VERSION_HIGHER = 27; // 0x1b
+ field public static final int LOG_EVENT_ID_SIGNATURE_MISMATCH = 29; // 0x1d
+ field public static final int LOG_EVENT_ID_SYSTEM_APP_NO_AGENT = 38; // 0x26
+ field public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; // 0x32
+ field public static final int LOG_EVENT_ID_UNKNOWN_VERSION = 44; // 0x2c
+ field public static final int LOG_EVENT_ID_VERSIONS_MATCH = 35; // 0x23
field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24
+ field public static final int LOG_EVENT_ID_WIDGET_METADATA_MISMATCH = 47; // 0x2f
+ field public static final int LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION = 48; // 0x30
}
public abstract class BackupObserver {
@@ -8898,8 +8945,10 @@
field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+ field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8910,7 +8959,6 @@
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
- field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -10895,6 +10943,7 @@
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean hasSystemFeature(java.lang.String, int);
method public abstract boolean isInstantApp();
+ method public abstract boolean isInstantApp(java.lang.String);
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -11079,8 +11128,11 @@
field public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; // 0xffffff9c
field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99
field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a
+ field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2
+ field public static final int INSTALL_REASON_DEVICE_SETUP = 3; // 0x3
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
+ field public static final int INSTALL_REASON_USER = 4; // 0x4
field public static final int INSTALL_SUCCEEDED = 1; // 0x1
field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
@@ -14465,12 +14517,12 @@
}
public final class Icon implements android.os.Parcelable {
+ method public static android.graphics.drawable.Icon createWithAdaptiveBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithContentUri(java.lang.String);
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
- method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
method public int describeContents();
@@ -32727,12 +32779,12 @@
field public static final int BATTERY_PLUGGED_AC = 1; // 0x1
field public static final int BATTERY_PLUGGED_USB = 2; // 0x2
field public static final int BATTERY_PLUGGED_WIRELESS = 4; // 0x4
- field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_PROPERTY_CAPACITY = 4; // 0x4
field public static final int BATTERY_PROPERTY_CHARGE_COUNTER = 1; // 0x1
field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3
field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2
field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5
+ field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2
field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
@@ -39335,7 +39387,6 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
- field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -39387,6 +39438,7 @@
public abstract class AutoFillService extends android.app.Service {
ctor public AutoFillService();
+ method public final void disableSelf();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -39449,6 +39501,7 @@
method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
}
}
@@ -39920,6 +39973,7 @@
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final java.lang.String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
+ field public static final java.lang.String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
field public static final java.lang.String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
}
@@ -41282,6 +41336,7 @@
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -42041,6 +42096,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isRinging();
@@ -42084,6 +42140,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -42814,6 +42871,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getImei();
@@ -43713,6 +43771,7 @@
method public boolean hasSystemFeature(java.lang.String);
method public boolean hasSystemFeature(java.lang.String, int);
method public boolean isInstantApp();
+ method public boolean isInstantApp(java.lang.String);
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -48154,6 +48213,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -48471,6 +48531,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -48612,6 +48673,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -49264,6 +49339,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -49281,6 +49357,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
@@ -50535,10 +50612,11 @@
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
+ method public boolean isDate();
method public boolean isList();
method public boolean isText();
method public boolean isToggle();
@@ -50548,9 +50626,11 @@
public final class AutoFillValue implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forDate(long);
method public static android.view.autofill.AutoFillValue forList(int);
method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public long getDateValue();
method public int getListValue();
method public java.lang.CharSequence getTextValue();
method public boolean getToggleValue();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1a12b4c..4585b8e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -298,6 +298,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -1005,7 +1006,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
- field public static final int primaryContentAlpha = 16843367; // 0x1010267
+ field public static final int primaryContentAlpha = 16844117; // 0x1010555
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -1061,7 +1062,9 @@
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
+ field public static final int requiredFeature = 16844119; // 0x1010557
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
+ field public static final int requiredNotFeature = 16844120; // 0x1010558
field public static final int requiresFadingEdge = 16843685; // 0x10103a5
field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
field public static final int resizeClip = 16843983; // 0x10104cf
@@ -1130,7 +1133,7 @@
field public static final int searchSuggestSelection = 16843224; // 0x10101d8
field public static final int searchSuggestThreshold = 16843373; // 0x101026d
field public static final int searchViewStyle = 16843904; // 0x1010480
- field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
+ field public static final int secondaryContentAlpha = 16844118; // 0x1010556
field public static final int secondaryProgress = 16843064; // 0x1010138
field public static final int secondaryProgressTint = 16843879; // 0x1010467
field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -3918,8 +3921,9 @@
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR;
- field public static final int IMPORTANCE_BACKGROUND = 400; // 0x190
- field public static final int IMPORTANCE_EMPTY = 500; // 0x1f4
+ field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190
+ field public static final int IMPORTANCE_CACHED = 400; // 0x190
+ field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4
field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64
field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d
field public static final int IMPORTANCE_GONE = 1000; // 0x3e8
@@ -5492,6 +5496,7 @@
public final class NotificationChannel implements android.os.Parcelable {
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
+ ctor public NotificationChannel(java.lang.String, int, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
method public boolean canShowBadge();
@@ -5505,6 +5510,7 @@
method public int getLightColor();
method public int getLockscreenVisibility();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
@@ -5524,12 +5530,14 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -6259,6 +6267,7 @@
method public long getMaximumTimeToLock(android.content.ComponentName);
method public int getOrganizationColor(android.content.ComponentName);
method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(android.os.UserHandle);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
@@ -6575,6 +6584,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -6591,6 +6601,7 @@
method public java.lang.String getIdEntry();
method public java.lang.String getIdPackage();
method public java.lang.String getIdType();
+ method public int getInputType();
method public int getLeft();
method public int getScrollX();
method public int getScrollY();
@@ -8472,8 +8483,10 @@
field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+ field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+ field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8484,7 +8497,6 @@
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
- field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -10379,6 +10391,7 @@
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean hasSystemFeature(java.lang.String, int);
method public abstract boolean isInstantApp();
+ method public abstract boolean isInstantApp(java.lang.String);
method public abstract boolean isPermissionReviewModeEnabled();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
@@ -10511,8 +10524,11 @@
field public static final int GET_SIGNATURES = 64; // 0x40
field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800
+ field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2
+ field public static final int INSTALL_REASON_DEVICE_SETUP = 3; // 0x3
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
+ field public static final int INSTALL_REASON_USER = 4; // 0x4
field public static final int MATCH_ALL = 131072; // 0x20000
field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000
@@ -13867,12 +13883,12 @@
}
public final class Icon implements android.os.Parcelable {
+ method public static android.graphics.drawable.Icon createWithAdaptiveBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithContentUri(java.lang.String);
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
- method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
method public int describeContents();
@@ -30221,12 +30237,12 @@
field public static final int BATTERY_PLUGGED_AC = 1; // 0x1
field public static final int BATTERY_PLUGGED_USB = 2; // 0x2
field public static final int BATTERY_PLUGGED_WIRELESS = 4; // 0x4
- field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_PROPERTY_CAPACITY = 4; // 0x4
field public static final int BATTERY_PROPERTY_CHARGE_COUNTER = 1; // 0x1
field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3
field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2
field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5
+ field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6
field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2
field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
@@ -36550,7 +36566,6 @@
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
field public static final int PURPOSE_VERIFY = 8; // 0x8
- field public static final int PURPOSE_WRAP_KEY = 16; // 0x10
field public static final java.lang.String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
field public static final java.lang.String SIGNATURE_PADDING_RSA_PSS = "PSS";
}
@@ -36602,6 +36617,7 @@
public abstract class AutoFillService extends android.app.Service {
ctor public AutoFillService();
+ method public final void disableSelf();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -36664,6 +36680,7 @@
method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutoFillId...);
method public android.service.autofill.SaveInfo build();
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
}
}
@@ -37106,6 +37123,7 @@
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final java.lang.String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
+ field public static final java.lang.String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
field public static final java.lang.String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
}
@@ -38415,6 +38433,7 @@
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -38984,6 +39003,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -39020,6 +39040,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -39707,6 +39728,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getLine1Number();
@@ -40558,6 +40580,7 @@
method public boolean hasSystemFeature(java.lang.String);
method public boolean hasSystemFeature(java.lang.String, int);
method public boolean isInstantApp();
+ method public boolean isInstantApp(java.lang.String);
method public boolean isPermissionReviewModeEnabled();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
@@ -45160,6 +45183,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45480,6 +45504,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -45621,6 +45646,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -46277,6 +46316,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -46294,6 +46334,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
@@ -47547,10 +47588,11 @@
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
+ method public boolean isDate();
method public boolean isList();
method public boolean isText();
method public boolean isToggle();
@@ -47560,9 +47602,11 @@
public final class AutoFillValue implements android.os.Parcelable {
method public int describeContents();
+ method public static android.view.autofill.AutoFillValue forDate(long);
method public static android.view.autofill.AutoFillValue forList(int);
method public static android.view.autofill.AutoFillValue forText(java.lang.CharSequence);
method public static android.view.autofill.AutoFillValue forToggle(boolean);
+ method public long getDateValue();
method public int getListValue();
method public java.lang.CharSequence getTextValue();
method public boolean getToggleValue();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fda9966..cae37c6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3024,14 +3024,22 @@
/**
* Constant for {@link #importance}: This process process contains
- * background code that is expendable.
+ * cached code that is expendable, not actively running any app components
+ * we care about.
*/
- public static final int IMPORTANCE_BACKGROUND = 400;
+ public static final int IMPORTANCE_CACHED = 400;
+
+ /**
+ * @deprecated Renamed to {@link #IMPORTANCE_CACHED}.
+ */
+ public static final int IMPORTANCE_BACKGROUND = IMPORTANCE_CACHED;
/**
* Constant for {@link #importance}: This process is empty of any
* actively running code.
+ * @deprecated This value is no longer reported, use {@link #IMPORTANCE_CACHED} instead.
*/
+ @Deprecated
public static final int IMPORTANCE_EMPTY = 500;
/**
@@ -3044,7 +3052,7 @@
if (procState == PROCESS_STATE_NONEXISTENT) {
return IMPORTANCE_GONE;
} else if (procState >= PROCESS_STATE_HOME) {
- return IMPORTANCE_BACKGROUND;
+ return IMPORTANCE_CACHED;
} else if (procState >= PROCESS_STATE_SERVICE) {
return IMPORTANCE_SERVICE;
} else if (procState > PROCESS_STATE_HEAVY_WEIGHT) {
@@ -3066,7 +3074,7 @@
public static int importanceToProcState(int importance) {
if (importance == IMPORTANCE_GONE) {
return PROCESS_STATE_NONEXISTENT;
- } else if (importance >= IMPORTANCE_BACKGROUND) {
+ } else if (importance >= IMPORTANCE_CACHED) {
return PROCESS_STATE_HOME;
} else if (importance >= IMPORTANCE_SERVICE) {
return PROCESS_STATE_SERVICE;
@@ -3088,8 +3096,8 @@
/**
* The relative importance level that the system places on this
* process. May be one of {@link #IMPORTANCE_FOREGROUND},
- * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE},
- * {@link #IMPORTANCE_BACKGROUND}, or {@link #IMPORTANCE_EMPTY}. These
+ * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE}, or
+ * {@link #IMPORTANCE_CACHED}. These
* constants are numbered so that "more important" values are always
* smaller than "less important" values.
*/
@@ -3101,7 +3109,7 @@
* utility of processes within a category. This number means nothing
* except that a smaller values are more recently used (and thus
* more important). Currently an LRU value is only maintained for
- * the {@link #IMPORTANCE_BACKGROUND} category, though others may
+ * the {@link #IMPORTANCE_CACHED} category, though others may
* be maintained in the future.
*/
public int lru;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0608acb..64e2d49 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -805,9 +805,13 @@
@Override
public boolean isInstantApp() {
+ return isInstantApp(mContext.getPackageName());
+ }
+
+ @Override
+ public boolean isInstantApp(String packageName) {
try {
- return mPM.isInstantApp(mContext.getPackageName(),
- mContext.getUserId());
+ return mPM.isInstantApp(packageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7ee93d0..0ab4b80 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2254,11 +2254,10 @@
}
void setResources(Resources r) {
- if (mPackageInfo.getTargetSdkVersion() < VERSION_CODES.O) {
- mResources = new CompatResources(r, this);
- } else {
- mResources = r;
+ if (r instanceof CompatResources) {
+ ((CompatResources) r).setContext(this);
}
+ mResources = r;
}
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
index f62ab8d..780a922 100644
--- a/core/java/android/app/FragmentTransition.java
+++ b/core/java/android/app/FragmentTransition.java
@@ -780,8 +780,10 @@
names = inTransaction.mSharedElementTargetNames;
}
- inSharedElements.retainAll(names);
- if (sharedElementCallback != null) {
+ if (names != null) {
+ inSharedElements.retainAll(names);
+ }
+ if (names != null && sharedElementCallback != null) {
sharedElementCallback.onMapSharedElements(names, inSharedElements);
for (int i = names.size() - 1; i >= 0; i--) {
String name = names.get(i);
@@ -830,8 +832,9 @@
FragmentContainerTransition fragments,
Transition enterTransition, boolean inIsPop) {
BackStackRecord inTransaction = fragments.lastInTransaction;
- if (enterTransition != null && inTransaction.mSharedElementSourceNames != null &&
- !inTransaction.mSharedElementSourceNames.isEmpty()) {
+ if (enterTransition != null && inSharedElements != null
+ && inTransaction.mSharedElementSourceNames != null
+ && !inTransaction.mSharedElementSourceNames.isEmpty()) {
final String targetName = inIsPop
? inTransaction.mSharedElementSourceNames.get(0)
: inTransaction.mSharedElementTargetNames.get(0);
@@ -1096,7 +1099,9 @@
if (transition != null) {
viewList = new ArrayList<>();
View root = fragment.getView();
- root.captureTransitioningViews(viewList);
+ if (root != null) {
+ root.captureTransitioningViews(viewList);
+ }
if (sharedElements != null) {
viewList.removeAll(sharedElements);
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8538330..dad2061 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -522,6 +522,7 @@
void startLocalVoiceInteraction(in IBinder token, in Bundle options);
void stopLocalVoiceInteraction(in IBinder token);
boolean supportsLocalVoiceInteraction();
+ void notifyPinnedStackAnimationStarted();
void notifyPinnedStackAnimationEnded();
void removeStack(int stackId);
void makePackageIdle(String packageName, int userId);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 9834350..d8d4bb9 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -38,6 +38,11 @@
void onPinnedActivityRestartAttempt(String launchedFromPackage);
/**
+ * Called whenever the pinned stack is starting animating a resize.
+ */
+ void onPinnedStackAnimationStarted();
+
+ /**
* Called whenever the pinned stack is done animating a resize.
*/
void onPinnedStackAnimationEnded();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 64fc44b..3fc459e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4040,15 +4040,23 @@
}
/**
- * Construct a RemoteViews for the final notification header only
+ * Construct a RemoteViews for the final notification header only. This will not be
+ * colorized.
*
* @hide
*/
public RemoteViews makeNotificationHeader() {
+ Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED);
+ mN.extras.putBoolean(EXTRA_COLORIZED, false);
RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
R.layout.notification_template_header);
resetNotificationHeader(header);
bindNotificationHeader(header, false /* ambient */);
+ if (colorized != null) {
+ mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
+ } else {
+ mN.extras.remove(EXTRA_COLORIZED);
+ }
return header;
}
@@ -4167,21 +4175,14 @@
mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
}
}
- Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED);
- mN.extras.putBoolean(EXTRA_COLORIZED, false);
RemoteViews header = makeNotificationHeader();
-
+ header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
if (summary != null) {
mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
} else {
mN.extras.remove(EXTRA_SUB_TEXT);
}
- if (colorized != null) {
- mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
- } else {
- mN.extras.remove(EXTRA_COLORIZED);
- }
mN.color = color;
return header;
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1a51608..85e6b85 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -20,8 +20,10 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.annotation.SystemApi;
-import android.graphics.Color;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Parcel;
@@ -45,6 +47,7 @@
private static final String TAG_CHANNEL = "channel";
private static final String ATT_NAME = "name";
+ private static final String ATT_NAME_RES_ID = "name_res_id";
private static final String ATT_ID = "id";
private static final String ATT_DELETED = "deleted";
private static final String ATT_PRIORITY = "priority";
@@ -138,6 +141,7 @@
private final String mId;
private CharSequence mName;
+ private int mNameResId = 0;
private int mImportance = DEFAULT_IMPORTANCE;
private boolean mBypassDnd;
private int mLockscreenVisibility = DEFAULT_VISIBILITY;
@@ -156,7 +160,9 @@
* Creates a notification channel.
*
* @param id The id of the channel. Must be unique per package.
- * @param name The user visible name of the channel.
+ * @param name The user visible name of the channel. Unchangeable once created; use this
+ * constructor if the channel represents a user-defined category that does not
+ * need to be translated.
* @param importance The importance of the channel. This controls how interruptive notifications
* posted to this channel are. See e.g.
* {@link NotificationManager#IMPORTANCE_DEFAULT}.
@@ -167,6 +173,21 @@
this.mImportance = importance;
}
+ /**
+ * Creates a notification channel.
+ *
+ * @param id The id of the channel. Must be unique per package.
+ * @param nameResId The resource id of the string containing the channel name.
+ * @param importance The importance of the channel. This controls how interruptive notifications
+ * posted to this channel are. See e.g.
+ * {@link NotificationManager#IMPORTANCE_DEFAULT}.
+ */
+ public NotificationChannel(String id, @StringRes int nameResId, int importance) {
+ this.mId = id;
+ this.mNameResId = nameResId;
+ this.mImportance = importance;
+ }
+
protected NotificationChannel(Parcel in) {
if (in.readByte() != 0) {
mId = in.readString();
@@ -174,6 +195,7 @@
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mNameResId = in.readInt();
mImportance = in.readInt();
mBypassDnd = in.readByte() != 0;
mLockscreenVisibility = in.readInt();
@@ -206,6 +228,7 @@
dest.writeByte((byte) 0);
}
TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mNameResId);
dest.writeInt(mImportance);
dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
dest.writeInt(mLockscreenVisibility);
@@ -382,11 +405,18 @@
/**
* Returns the user visible name of this channel.
*/
- public CharSequence getName() {
+ public @Nullable CharSequence getName() {
return mName;
}
/**
+ * Returns the resource id of the user visible name of this channel.
+ */
+ public int getNameResId() {
+ return mNameResId;
+ }
+
+ /**
* Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
* notifications posted to this channel.
*/
@@ -516,7 +546,10 @@
public void writeXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_CHANNEL);
out.attribute(null, ATT_ID, getId());
- out.attribute(null, ATT_NAME, getName().toString());
+ if (getName() != null) {
+ out.attribute(null, ATT_NAME, getName().toString());
+ }
+ out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId()));
if (getImportance() != DEFAULT_IMPORTANCE) {
out.attribute(
null, ATT_IMPORTANCE, Integer.toString(getImportance()));
@@ -574,6 +607,7 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_NAME_RES_ID, getNameResId());
if (getImportance() != DEFAULT_IMPORTANCE) {
record.put(ATT_IMPORTANCE,
NotificationListenerService.Ranking.importanceToString(getImportance()));
@@ -691,6 +725,7 @@
NotificationChannel that = (NotificationChannel) o;
+ if (getNameResId() != that.getNameResId()) return false;
if (getImportance() != that.getImportance()) return false;
if (mBypassDnd != that.mBypassDnd) return false;
if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false;
@@ -720,6 +755,7 @@
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + getNameResId();
result = 31 * result + getImportance();
result = 31 * result + (mBypassDnd ? 1 : 0);
result = 31 * result + getLockscreenVisibility();
@@ -741,6 +777,7 @@
return "NotificationChannel{" +
"mId='" + mId + '\'' +
", mName=" + mName +
+ ", mNameResId=" + mNameResId +
", mImportance=" + mImportance +
", mBypassDnd=" + mBypassDnd +
", mLockscreenVisibility=" + mLockscreenVisibility +
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 8854adc..288d39a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -15,6 +15,7 @@
*/
package android.app;
+import android.annotation.StringRes;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
@@ -40,23 +41,38 @@
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
+ private static final String ATT_NAME_RES_ID = "name_res_id";
private static final String ATT_ID = "id";
private final String mId;
private CharSequence mName;
+ private int mNameResId = 0;
private List<NotificationChannel> mChannels = new ArrayList<>();
/**
* Creates a notification channel.
*
* @param id The id of the group. Must be unique per package.
- * @param name The user visible name of the group.
+ * @param name The user visible name of the group. Unchangeable once created; use this
+ * constructor if the group represents something user-defined that does not
+ * need to be translated.
*/
public NotificationChannelGroup(String id, CharSequence name) {
this.mId = id;
this.mName = name;
}
+ /**
+ * Creates a notification channel.
+ *
+ * @param id The id of the group. Must be unique per package.
+ * @param nameResId String resource id of the user visible name of the group.
+ */
+ public NotificationChannelGroup(String id, @StringRes int nameResId) {
+ this.mId = id;
+ this.mNameResId = nameResId;
+ }
+
protected NotificationChannelGroup(Parcel in) {
if (in.readByte() != 0) {
mId = in.readString();
@@ -64,6 +80,7 @@
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mNameResId = in.readInt();
in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
}
@@ -76,6 +93,7 @@
dest.writeByte((byte) 0);
}
TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mNameResId);
dest.writeParcelableList(mChannels, flags);
}
@@ -93,6 +111,13 @@
return mName;
}
+ /**
+ * Returns the resource id of the user visible name of this group.
+ */
+ public @StringRes int getNameResId() {
+ return mNameResId;
+ }
+
/*
* Returns the list of channels that belong to this group
*
@@ -119,7 +144,12 @@
out.startTag(null, TAG_GROUP);
out.attribute(null, ATT_ID, getId());
- out.attribute(null, ATT_NAME, getName().toString());
+ if (getName() != null) {
+ out.attribute(null, ATT_NAME, getName().toString());
+ }
+ if (getNameResId() != 0) {
+ out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId()));
+ }
out.endTag(null, TAG_GROUP);
}
@@ -132,6 +162,7 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_NAME_RES_ID, getNameResId());
return record;
}
@@ -160,6 +191,7 @@
NotificationChannelGroup that = (NotificationChannelGroup) o;
+ if (getNameResId() != that.getNameResId()) return false;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
@@ -171,13 +203,18 @@
@Override
public NotificationChannelGroup clone() {
- return new NotificationChannelGroup(getId(), getName());
+ if (getName() != null) {
+ return new NotificationChannelGroup(getId(), getName());
+ } else {
+ return new NotificationChannelGroup(getId(), getNameResId());
+ }
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + getNameResId();
result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
return result;
}
@@ -187,6 +224,8 @@
return "NotificationChannelGroup{" +
"mId='" + mId + '\'' +
", mName=" + mName +
+ ", mNameResId=" + mNameResId +
+ ", mChannels=" + mChannels +
'}';
}
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 52ec045..b42df5e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
+import android.content.res.CompatResources;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -447,7 +448,8 @@
* or the class loader is different.
*/
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
- @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
+ @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
+ @NonNull CompatibilityInfo compatInfo) {
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
@@ -466,7 +468,8 @@
}
}
- Resources resources = new Resources(classLoader);
+ Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
+ : new Resources(classLoader);
resources.setImpl(impl);
activityResources.activityResources.add(new WeakReference<>(resources));
if (DEBUG) {
@@ -481,7 +484,7 @@
* otherwise creates a new Resources object.
*/
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
- @NonNull ResourcesImpl impl) {
+ @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
// Find an existing Resources that has this ResourcesImpl set.
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
@@ -498,7 +501,8 @@
}
// Create a new Resources reference and use the existing ResourcesImpl object.
- Resources resources = new Resources(classLoader);
+ Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
+ : new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
@@ -614,7 +618,7 @@
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl);
+ resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
@@ -629,7 +633,7 @@
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
- return getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
@@ -659,9 +663,9 @@
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl);
+ resourcesImpl, key.mCompatInfo);
} else {
- resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 5a0845f..7a569fc 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -39,6 +39,10 @@
}
@Override
+ public void onPinnedStackAnimationStarted() throws RemoteException {
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() throws RemoteException {
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6585793..449cca3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7958,4 +7958,28 @@
throw re.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Called by the system to get a list of CA certificates that were installed by the device or
+ * profile owner.
+ *
+ * <p> The caller must be the target user's Device Owner/Profile owner or hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ *
+ * @param user The user for whom to retrieve information.
+ * @return list of aliases identifying CA certificates installed by the device or profile owner
+ * @throws SecurityException if the caller does not have permission to retrieve information
+ * about the given user's CA certificates.
+ *
+ * @hide
+ */
+ @TestApi
+ public List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ try {
+ return mService.getOwnerInstalledCaCerts(user).getList();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ec97c2c..97a4678 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.content.pm.StringParceledListSlice;
import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.net.Uri;
@@ -349,4 +350,5 @@
boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
boolean isDefaultInputMethodSetByOwner(in UserHandle user);
+ StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 483a7a1..a1ef4a6 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -26,6 +26,7 @@
import android.view.autofill.AutoFillValue;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Assist data automatically created by the platform's implementation
@@ -533,13 +534,16 @@
String mIdPackage;
String mIdType;
String mIdEntry;
+
// TODO(b/33197203): once we have more flags, it might be better to store the individual
// fields (viewId and childId) of the field.
AutoFillId mAutoFillId;
AutoFillType mAutoFillType;
+ @View.AutoFillHint int mAutoFillHint;
AutoFillValue mAutoFillValue;
String[] mAutoFillOptions;
boolean mSanitized;
+
int mX;
int mY;
int mScrollX;
@@ -581,6 +585,7 @@
static final int FLAGS_HAS_ID = 0x00200000;
static final int FLAGS_HAS_CHILDREN = 0x00100000;
static final int FLAGS_HAS_URL = 0x00080000;
+ static final int FLAGS_HAS_INPUT_TYPE = 0x00040000;
static final int FLAGS_ALL_CONTROL = 0xfff00000;
int mFlags;
@@ -589,6 +594,7 @@
CharSequence mContentDescription;
ViewNodeText mText;
+ int mInputType;
String mUrl;
Bundle mExtras;
@@ -618,6 +624,7 @@
mSanitized = in.readInt() == 1;
mAutoFillId = in.readParcelable(null);
mAutoFillType = in.readParcelable(null);
+ mAutoFillHint = in.readInt();
mAutoFillValue = in.readParcelable(null);
mAutoFillOptions = in.readStringArray();
}
@@ -655,6 +662,9 @@
if ((flags&FLAGS_HAS_TEXT) != 0) {
mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0);
}
+ if ((flags&FLAGS_HAS_INPUT_TYPE) != 0) {
+ mInputType = in.readInt();
+ }
if ((flags&FLAGS_HAS_URL) != 0) {
mUrl = in.readString();
}
@@ -711,6 +721,9 @@
flags |= FLAGS_HAS_COMPLEX_TEXT;
}
}
+ if (mInputType != 0) {
+ flags |= FLAGS_HAS_INPUT_TYPE;
+ }
if (mUrl != null) {
flags |= FLAGS_HAS_URL;
}
@@ -745,6 +758,7 @@
out.writeInt(mSanitized ? 1 : 0);
out.writeParcelable(mAutoFillId, 0);
out.writeParcelable(mAutoFillType, 0);
+ out.writeInt(mAutoFillHint);
final AutoFillValue sanitizedValue = writeSensitive ? mAutoFillValue : null;
out.writeParcelable(sanitizedValue, 0);
out.writeStringArray(mAutoFillOptions);
@@ -778,6 +792,10 @@
if ((flags&FLAGS_HAS_TEXT) != 0) {
mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0, writeSensitive);
}
+ if ((flags&FLAGS_HAS_INPUT_TYPE) != 0) {
+ out.writeInt(mInputType);
+ }
+
if ((flags&FLAGS_HAS_URL) != 0) {
out.writeString(mUrl);
}
@@ -844,6 +862,20 @@
}
/**
+ * Describes the content of a view so that a auto-fill service can fill in the appropriate
+ * data.
+ *
+ * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
+ * for assist.</p>
+ *
+ * @return The hint for this view
+ */
+ // TODO(b/35364993): add CTS/unit test
+ @View.AutoFillHint public int getAutoFillHint() {
+ return mAutoFillHint;
+ }
+
+ /**
* Gets the the value of this view.
*
* <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
@@ -867,6 +899,15 @@
return mAutoFillOptions;
}
+ /**
+ * Gets the {@link android.text.InputType} bits of this structure.
+ *
+ * @return bits as defined by {@link android.text.InputType}.
+ */
+ public int getInputType() {
+ return mInputType;
+ }
+
/** @hide */
public boolean isSanitized() {
return mSanitized;
@@ -1525,6 +1566,11 @@
}
@Override
+ public void setAutoFillHint(@View.AutoFillHint int hint) {
+ mNode.mAutoFillHint = hint;
+ }
+
+ @Override
public void setAutoFillValue(AutoFillValue value) {
mNode.mAutoFillValue = value;
}
@@ -1534,6 +1580,11 @@
mNode.mAutoFillOptions = options;
}
+ @Override
+ public void setInputType(int inputType) {
+ mNode.mInputType = inputType;
+ }
+
/**
* @hide
*/
@@ -1664,6 +1715,9 @@
} else {
Log.i(TAG, prefix + "AutoFill info: id= " + autoFillId
+ ", type=" + node.getAutoFillType()
+ + ", options=" + Arrays.toString(node.getAutoFillOptions())
+ + ", inputType=" + node.getInputType()
+ + ", hint=" + Integer.toHexString(node.getAutoFillHint())
+ ", value=" + node.getAutoFillValue()
+ ", sanitized=" + node.isSanitized());
}
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index d2a623e..ebad16e 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -54,26 +54,118 @@
public static final String EXTRA_LOG_EVENT_CATEGORY =
"android.app.backup.extra.LOG_EVENT_CATEGORY";
+
/**
- * string: when we have event of id LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER we send the version
+ * boolean: when we have an event with id LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL we record if
+ * the call was to cancel backup of all packages
+ */
+ public static final String EXTRA_LOG_CANCEL_ALL = "android.app.backup.extra.LOG_CANCEL_ALL";
+
+ /**
+ * string: when we have an event with id LOG_EVENT_ID_ILLEGAL_KEY we send the key that was used
+ * by the app
+ */
+ public static final String EXTRA_LOG_ILLEGAL_KEY = "android.app.backup.extra.LOG_ILLEGAL_KEY";
+
+ /**
+ * long: when we have an event with id LOG_EVENT_ID_ERROR_PREFLIGHT we send the error code that
+ * was returned by the transport during preflight
+ */
+ public static final String EXTRA_LOG_PREFLIGHT_ERROR =
+ "android.app.backup.extra.LOG_PREFLIGHT_ERROR";
+
+ /**
+ * string: when we have an event with id LOG_EVENT_ID_EXCEPTION_FULL_BACKUP we send the
+ * exception's stacktrace
+ */
+ public static final String EXTRA_LOG_EXCEPTION_FULL_BACKUP =
+ "android.app.backup.extra.LOG_EXCEPTION_FULL_BACKUP";
+
+ /**
+ * int: when we have an event with id LOG_EVENT_ID_RESTORE_VERSION_HIGHER we send the
+ * restore package version
+ */
+ public static final String EXTRA_LOG_RESTORE_VERSION =
+ "android.app.backup.extra.LOG_RESTORE_VERSION";
+
+ /**
+ * boolean: when we have an event with id LOG_EVENT_ID_RESTORE_VERSION_HIGHER we record if
+ * ApplicationInfo.FLAG_RESTORE_ANY_VERSION flag is set
+ */
+ public static final String EXTRA_LOG_RESTORE_ANYWAY =
+ "android.app.backup.extra.LOG_RESTORE_ANYWAY";
+
+
+ /**
+ * boolean: when we have an event with id LOG_EVENT_ID_APK_NOT_INSTALLED we record if
+ * the policy allows to install apks provided with the dataset
+ */
+ public static final String EXTRA_LOG_POLICY_ALLOW_APKS =
+ "android.app.backup.extra.LOG_POLICY_ALLOW_APKS";
+
+
+ /**
+ * string: when we have an event with id LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE we record the
+ * package name provided in the restore manifest
+ */
+ public static final String EXTRA_LOG_MANIFEST_PACKAGE_NAME =
+ "android.app.backup.extra.LOG_MANIFEST_PACKAGE_NAME";
+
+ /**
+ * string: when we have an event with id LOG_EVENT_ID_WIDGET_METADATA_MISMATCH we record the
+ * package name provided in the widget metadata
+ */
+ public static final String EXTRA_LOG_WIDGET_PACKAGE_NAME =
+ "android.app.backup.extra.LOG_WIDGET_PACKAGE_NAME";
+
+ /**
+ * int: when we have event of id LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER we send the version
* of the backup.
*/
- public static final String EXTRA_LOG_OLD_VERSION =
- "android.app.backup.extra.LOG_OLD_VERSION";
+ public static final String EXTRA_LOG_OLD_VERSION = "android.app.backup.extra.LOG_OLD_VERSION";
// TODO complete this list with all log messages. And document properly.
- public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4;
+ public static final int LOG_EVENT_ID_FULL_BACKUP_CANCEL = 4;
+ public static final int LOG_EVENT_ID_ILLEGAL_KEY = 5;
+ public static final int LOG_EVENT_ID_NO_DATA_TO_SEND = 7;
+ public static final int LOG_EVENT_ID_PACKAGE_INELIGIBLE = 9;
+ public static final int LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT = 10;
+ public static final int LOG_EVENT_ID_PACKAGE_STOPPED = 11;
public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12;
+ public static final int LOG_EVENT_ID_BACKUP_DISABLED = 13;
+ public static final int LOG_EVENT_ID_DEVICE_NOT_PROVISIONED = 14;
public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15;
- public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21;
+ public static final int LOG_EVENT_ID_ERROR_PREFLIGHT = 16;
+ public static final int LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT = 18;
+ public static final int LOG_EVENT_ID_EXCEPTION_FULL_BACKUP = 19;
+ public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL = 21;
public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22;
+ public static final int LOG_EVENT_ID_NO_PM_METADATA_RECEIVED = 23;
+ public static final int LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA = 24;
+ public static final int LOG_EVENT_ID_LOST_TRANSPORT = 25;
public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26;
+ public static final int LOG_EVENT_ID_RESTORE_VERSION_HIGHER = 27;
public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28;
+ public static final int LOG_EVENT_ID_SIGNATURE_MISMATCH = 29;
public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30;
public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31;
+ public static final int LOG_EVENT_ID_RESTORE_ANY_VERSION = 34;
+ public static final int LOG_EVENT_ID_VERSIONS_MATCH = 35;
public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36;
+ public static final int LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH = 37;
+ public static final int LOG_EVENT_ID_SYSTEM_APP_NO_AGENT = 38;
+ public static final int LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE = 39;
+ public static final int LOG_EVENT_ID_APK_NOT_INSTALLED = 40;
+ public static final int LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK = 41;
+ public static final int LOG_EVENT_ID_MISSING_SIGNATURE = 42;
+ public static final int LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE = 43;
+ public static final int LOG_EVENT_ID_UNKNOWN_VERSION = 44;
public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45;
+ public static final int LOG_EVENT_ID_CORRUPT_MANIFEST = 46;
+ public static final int LOG_EVENT_ID_WIDGET_METADATA_MISMATCH = 47;
+ public static final int LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION = 48;
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
+ public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50;
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 4b6f99b..1d5c2b0 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -24,8 +24,10 @@
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/**
- * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
+ * CacheQuotaHint represents a triplet of a uid, the volume UUID it is stored upon, and
* its usage stats. When processed, it obtains a cache quota as defined by the system which
* allows apps to understand how much cache to use.
* {@hide}
@@ -78,6 +80,23 @@
return 0;
}
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CacheQuotaHint) {
+ final CacheQuotaHint other = (CacheQuotaHint) o;
+ return Objects.equals(mUuid, other.mUuid)
+ && Objects.equals(mUsageStats, other.mUsageStats)
+ && mUid == other.mUid && mQuota == other.mQuota;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.mUuid, this.mUid, this.mUsageStats, this.mQuota);
+ }
+
public static final class Builder {
private String mUuid;
private int mUid;
@@ -100,7 +119,7 @@
}
public @NonNull Builder setUid(int uid) {
- Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
+ Preconditions.checkArgumentNonnegative(uid, "Proposed uid was negative.");
mUid = uid;
return this;
}
@@ -117,7 +136,6 @@
}
public @NonNull CacheQuotaHint build() {
- Preconditions.checkNotNull(mUsageStats);
return new CacheQuotaHint(this);
}
}
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 4252482..7d8459c 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -50,6 +50,12 @@
public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
"android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
+ /* Extras used in ACTION_MESSAGE_RECEIVED intent */
+ public static final String EXTRA_SENDER_CONTACT_URI =
+ "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
+ public static final String EXTRA_SENDER_CONTACT_NAME =
+ "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
+
private IBluetoothMapClient mService;
private final Context mContext;
private ServiceListener mServiceListener;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9958a79..5579c9a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -307,6 +307,18 @@
*/
public static final String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
+ /**
+ * Allows provider to report back to client which keys were honored.
+ *
+ * Key identifying a {@code String[]} containing all QUERY_ARG_SORT* arguments
+ * honored by the provider. Include this in {@link Cursor} extras {@link Bundle}
+ * when any QUERY_ARG_SORT* value was honored during the preparation of the
+ * results {@link Cursor}.
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS, #QUERY_ARG_SORT_DIRECTION, #QUERY_ARG_SORT_COLLATION.
+ */
+ public static final String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
+
/** @hide */
@IntDef(flag = false, value = {
QUERY_SORT_DIRECTION_ASCENDING,
@@ -354,8 +366,9 @@
public static final String QUERY_ARG_LIMIT = "android:query-page-limit";
/**
- * Added to {@link Cursor} extras {@link Bundle} to indicate size of the
- * full, un-offset, un-limited recordset.
+ * Added to {@link Cursor} extras {@link Bundle} to indicate total size of
+ * recordset when paging is active. Providers must include this when
+ * implementing paging support.
*
* <p>When full size of the recordset is unknown a provider may return -1
* to indicate this.
@@ -364,7 +377,7 @@
* send content change notification once (if) full recordset size becomes
* known.
*/
- public static final String QUERY_RESULT_SIZE = "android:query-result-size";
+ public static final String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
/**
* This is the Android platform's base MIME type for a content: URI
@@ -704,6 +717,13 @@
* <li>Provide an explicit projection, to prevent reading data from storage
* that aren't going to be used.
*
+ * Provider must identify which QUERY_ARG_SORT* arguments were honored during
+ * the preparation of the result set by including the respective argument keys
+ * in the {@link Cursor} extras {@link Bundle}. See {@link #EXTRA_HONORED_ARGS}
+ * for details.
+ *
+ * @see #QUERY_ARG_SORT_COLUMNS, #QUERY_ARG_SORT_DIRECTION, #QUERY_ARG_SORT_COLLATION.
+ *
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
@@ -3037,17 +3057,19 @@
query += " COLLATE NOCASE";
}
- switch (queryArgs.getInt(
- QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE)) {
- case QUERY_SORT_DIRECTION_ASCENDING:
- query += " ASC";
- break;
- case QUERY_SORT_DIRECTION_DESCENDING:
- query += " DESC";
- break;
- default:
- throw new IllegalArgumentException("Unsupported sort direction value."
- + " See ContentResolver documentation for details.");
+ int sortDir = queryArgs.getInt(QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE);
+ if (sortDir != Integer.MIN_VALUE) {
+ switch (sortDir) {
+ case QUERY_SORT_DIRECTION_ASCENDING:
+ query += " ASC";
+ break;
+ case QUERY_SORT_DIRECTION_DESCENDING:
+ query += " DESC";
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported sort direction value."
+ + " See ContentResolver documentation for details.");
+ }
}
return query;
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 776492a..c3fd089 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -829,7 +829,7 @@
final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
if (bmp != null) {
BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
- if (shortcut.hasMaskableBitmap()) {
+ if (shortcut.hasAdaptiveBitmap()) {
return new AdaptiveIconDrawable(null, dr);
} else {
return dr;
@@ -854,7 +854,7 @@
icon.getResId(), shortcut.getUserHandle(), density);
}
case Icon.TYPE_BITMAP:
- case Icon.TYPE_BITMAP_MASKABLE: {
+ case Icon.TYPE_ADAPTIVE_BITMAP: {
return icon.loadDrawable(mContext);
}
default:
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0482f51..b3b5bcf 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -745,7 +745,8 @@
public static final int DONT_KILL_APP = 0x00000001;
/** @hide */
- @IntDef({INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY})
+ @IntDef({INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY, INSTALL_REASON_DEVICE_RESTORE,
+ INSTALL_REASON_DEVICE_SETUP, INSTALL_REASON_USER})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallReason {}
@@ -760,6 +761,21 @@
public static final int INSTALL_REASON_POLICY = 1;
/**
+ * Code indicating that this package was installed as part of restoring from another device.
+ */
+ public static final int INSTALL_REASON_DEVICE_RESTORE = 2;
+
+ /**
+ * Code indicating that this package was installed as part of device setup.
+ */
+ public static final int INSTALL_REASON_DEVICE_SETUP = 3;
+
+ /**
+ * Code indicating that the package installation was initiated by the user.
+ */
+ public static final int INSTALL_REASON_USER = 4;
+
+ /**
* Installation return code: this is passed to the
* {@link IPackageInstallObserver} on success.
*
@@ -3737,10 +3753,11 @@
public abstract @Nullable Drawable getInstantAppIcon(String packageName);
/**
- * Gets whether the caller is an instant app.
+ * Gets whether this application is an instant app.
*
* @return Whether caller is an instant app.
*
+ * @see #isInstantApp(String)
* @see #setInstantAppCookie(byte[])
* @see #getInstantAppCookie()
* @see #getInstantAppCookieMaxSize()
@@ -3748,12 +3765,26 @@
public abstract boolean isInstantApp();
/**
+ * Gets whether the given package is an instant app.
+ *
+ * @param packageName The package to check
+ * @return Whether the given package is an instant app.
+ *
+ * @see #isInstantApp()
+ * @see #setInstantAppCookie(byte[])
+ * @see #getInstantAppCookie()
+ * @see #getInstantAppCookieMaxSize()
+ */
+ public abstract boolean isInstantApp(String packageName);
+
+ /**
* Gets the maximum size in bytes of the cookie data an instant app
* can store on the device.
*
* @return The max cookie size in bytes.
*
* @see #isInstantApp()
+ * @see #isInstantApp(String)
* @see #setInstantAppCookie(byte[])
* @see #getInstantAppCookie()
*/
@@ -3770,6 +3801,7 @@
* @return The cookie.
*
* @see #isInstantApp()
+ * @see #isInstantApp(String)
* @see #setInstantAppCookie(byte[])
* @see #getInstantAppCookieMaxSize()
*/
@@ -3792,6 +3824,7 @@
* @return Whether the cookie was set.
*
* @see #isInstantApp()
+ * @see #isInstantApp(String)
* @see #getInstantAppCookieMaxSize()
* @see #getInstantAppCookie()
*/
@@ -4983,6 +5016,7 @@
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, @PackageInfoFlags int flags) {
final PackageParser parser = new PackageParser();
+ parser.setCallback(new PackageParser.CallbackImpl(this));
final File apkFile = new File(archiveFilePath);
try {
if ((flags & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
@@ -6108,6 +6142,9 @@
*
* @see #INSTALL_REASON_UNKNOWN
* @see #INSTALL_REASON_POLICY
+ * @see #INSTALL_REASON_DEVICE_RESTORE
+ * @see #INSTALL_REASON_DEVICE_SETUP
+ * @see #INSTALL_REASON_USER
*
* @hide
*/
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f801e45..60cc6b0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -285,6 +285,7 @@
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
private DisplayMetrics mMetrics;
+ private Callback mCallback;
private File mCacheDir;
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
@@ -506,6 +507,37 @@
mCacheDir = cacheDir;
}
+ /**
+ * Callback interface for retrieving information that may be needed while parsing
+ * a package.
+ */
+ public interface Callback {
+ boolean hasFeature(String feature);
+ }
+
+ /**
+ * Standard implementation of {@link Callback} on top of the public {@link PackageManager}
+ * class.
+ */
+ public static final class CallbackImpl implements Callback {
+ private final PackageManager mPm;
+
+ public CallbackImpl(PackageManager pm) {
+ mPm = pm;
+ }
+
+ @Override public boolean hasFeature(String feature) {
+ return mPm.hasSystemFeature(feature);
+ }
+ }
+
+ /**
+ * Set the {@link Callback} that can be used while parsing.
+ */
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
public static final boolean isApkFile(File file) {
return isApkPath(file.getName());
}
@@ -2079,15 +2111,15 @@
return null;
}
} else if (tagName.equals(TAG_PERMISSION_GROUP)) {
- if (parsePermissionGroup(pkg, flags, res, parser, outError) == null) {
+ if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_PERMISSION)) {
- if (parsePermission(pkg, res, parser, outError) == null) {
+ if (!parsePermission(pkg, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_PERMISSION_TREE)) {
- if (parsePermissionTree(pkg, res, parser, outError) == null) {
+ if (!parsePermissionTree(pkg, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_USES_PERMISSION)) {
@@ -2708,22 +2740,44 @@
}
}
+ final String requiredFeature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
+
+ final String requiredNotfeature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredNotFeature, 0);
+
sa.recycle();
- if ((maxSdkVersion == 0) || (maxSdkVersion >= Build.VERSION.RESOURCES_SDK_INT)) {
- if (name != null) {
- int index = pkg.requestedPermissions.indexOf(name);
- if (index == -1) {
- pkg.requestedPermissions.add(name.intern());
- } else {
- Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
- + name + " in package: " + pkg.packageName + " at: "
- + parser.getPositionDescription());
- }
- }
+ XmlUtils.skipCurrentTag(parser);
+
+ if (name == null) {
+ return true;
}
- XmlUtils.skipCurrentTag(parser);
+ if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+ return true;
+ }
+
+ // Only allow requesting this permission if the platform supports the given feature.
+ if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(requiredFeature)) {
+ return true;
+ }
+
+ // Only allow requesting this permission if the platform doesn't support the given feature.
+ if (requiredNotfeature != null && mCallback != null
+ && mCallback.hasFeature(requiredNotfeature)) {
+ return true;
+ }
+
+ int index = pkg.requestedPermissions.indexOf(name);
+ if (index == -1) {
+ pkg.requestedPermissions.add(name.intern());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.packageName + " at: "
+ + parser.getPositionDescription());
+ }
+
return true;
}
@@ -2951,7 +3005,7 @@
return true;
}
- private PermissionGroup parsePermissionGroup(Package owner, int flags, Resources res,
+ private boolean parsePermissionGroup(Package owner, int flags, Resources res,
XmlResourceParser parser, String[] outError)
throws XmlPullParserException, IOException {
PermissionGroup perm = new PermissionGroup(owner);
@@ -2968,7 +3022,7 @@
com.android.internal.R.styleable.AndroidManifestPermissionGroup_banner)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
perm.info.descriptionRes = sa.getResourceId(
@@ -2987,22 +3041,22 @@
if (!parseAllMetaData(res, parser, "<permission-group>", perm,
outError)) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
owner.permissionGroups.add(perm);
- return perm;
+ return true;
}
- private Permission parsePermission(Package owner, Resources res,
+ private boolean parsePermission(Package owner, Resources res,
XmlResourceParser parser, String[] outError)
throws XmlPullParserException, IOException {
- Permission perm = new Permission(owner);
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestPermission);
+ Permission perm = new Permission(owner);
if (!parsePackageItemInfo(owner, perm.info, outError,
"<permission>", sa, true /*nameRequired*/,
com.android.internal.R.styleable.AndroidManifestPermission_name,
@@ -3013,7 +3067,7 @@
com.android.internal.R.styleable.AndroidManifestPermission_banner)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
// Note: don't allow this value to be a reference to a resource
@@ -3040,7 +3094,7 @@
if (perm.info.protectionLevel == -1) {
outError[0] = "<permission> does not specify protectionLevel";
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel);
@@ -3052,21 +3106,21 @@
outError[0] = "<permission> protectionLevel specifies a non-ephemeral flag but is "
+ "not based on signature type";
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
}
if (!parseAllMetaData(res, parser, "<permission>", perm, outError)) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
owner.permissions.add(perm);
- return perm;
+ return true;
}
- private Permission parsePermissionTree(Package owner, Resources res,
+ private boolean parsePermissionTree(Package owner, Resources res,
XmlResourceParser parser, String[] outError)
throws XmlPullParserException, IOException {
Permission perm = new Permission(owner);
@@ -3084,7 +3138,7 @@
com.android.internal.R.styleable.AndroidManifestPermissionTree_banner)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
sa.recycle();
@@ -3097,7 +3151,7 @@
outError[0] = "<permission-tree> name has less than three segments: "
+ perm.info.name;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
perm.info.descriptionRes = 0;
@@ -3107,12 +3161,12 @@
if (!parseAllMetaData(res, parser, "<permission-tree>", perm,
outError)) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
- return null;
+ return false;
}
owner.permissions.add(perm);
- return perm;
+ return true;
}
private Instrumentation parseInstrumentation(Package owner, Resources res,
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index d3d3c66..5201694 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -96,7 +96,7 @@
public static final int FLAG_IMMUTABLE = 1 << 8;
/** @hide */
- public static final int FLAG_MASKABLE_BITMAP = 1 << 9;
+ public static final int FLAG_ADAPTIVE_BITMAP = 1 << 9;
/** @hide */
public static final int FLAG_CHOOSER = 1 << 10;
@@ -118,7 +118,7 @@
FLAG_DISABLED,
FLAG_STRINGS_RESOLVED,
FLAG_IMMUTABLE,
- FLAG_MASKABLE_BITMAP,
+ FLAG_ADAPTIVE_BITMAP,
FLAG_CHOOSER,
})
@Retention(RetentionPolicy.SOURCE)
@@ -784,7 +784,7 @@
switch (icon.getType()) {
case Icon.TYPE_RESOURCE:
case Icon.TYPE_BITMAP:
- case Icon.TYPE_BITMAP_MASKABLE:
+ case Icon.TYPE_ADAPTIVE_BITMAP:
break; // OK
default:
throw getInvalidIconException();
@@ -917,7 +917,7 @@
* and will be ignored.
*
* <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)},
- * {@link Icon#createWithMaskableBitmap(Bitmap)}
+ * {@link Icon#createWithAdaptiveBitmap(Bitmap)}
* and {@link Icon#createWithResource} are supported.
* Other types, such as URI-based icons, are not supported.
*
@@ -1615,12 +1615,13 @@
}
/**
- * Return whether a shortcut's icon is maskable.
+ * Return whether a shortcut's icon is adaptive bitmap following design guideline
+ * defined in {@link AdaptiveIconDrawable}.
*
* @hide internal/unit tests only
*/
- public boolean hasMaskableBitmap() {
- return hasFlags(FLAG_MASKABLE_BITMAP);
+ public boolean hasAdaptiveBitmap() {
+ return hasFlags(FLAG_ADAPTIVE_BITMAP);
}
/**
diff --git a/core/java/android/content/res/CompatResources.java b/core/java/android/content/res/CompatResources.java
index 15575fd..829b6b7 100644
--- a/core/java/android/content/res/CompatResources.java
+++ b/core/java/android/content/res/CompatResources.java
@@ -27,11 +27,17 @@
*/
public class CompatResources extends Resources {
- private final WeakReference<Context> mContext;
+ private WeakReference<Context> mContext;
- public CompatResources(Resources base, Context context) {
- super(base.getClassLoader());
- setImpl(base.getImpl());
+ public CompatResources(ClassLoader cls) {
+ super(cls);
+ mContext = new WeakReference<>(null);
+ }
+
+ /**
+ * @hide
+ */
+ public void setContext(Context context) {
mContext = new WeakReference<>(context);
}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index da35ee9..781e235 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -21,6 +21,8 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
@@ -78,6 +80,11 @@
private static final int NEEDS_SCREEN_COMPAT = 8;
/**
+ * Set if the application needs to run in with compat resources.
+ */
+ private static final int NEEDS_COMPAT_RES = 16;
+
+ /**
* The effective screen density we have selected for this application.
*/
public final int applicationDensity;
@@ -96,6 +103,9 @@
boolean forceCompat) {
int compatFlags = 0;
+ if (appInfo.targetSdkVersion < VERSION_CODES.O) {
+ compatFlags |= NEEDS_COMPAT_RES;
+ }
if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
|| appInfo.largestWidthLimitDp != 0) {
// New style screen requirements spec.
@@ -274,6 +284,10 @@
return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
}
+ public boolean needsCompatResources() {
+ return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0;
+ }
+
/**
* Returns the translator which translates the coordinates in compatibility mode.
* @param params the window's parameter
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1259de6..72f51cf 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2913,11 +2913,14 @@
if (callback == null) {
throw new IllegalArgumentException("null NetworkCallback");
}
- if (need == null && action != REQUEST) {
+ if ((need == null) && (action != REQUEST)) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
- // TODO: throw an exception if callback.networkRequest is not null.
- // http://b/20701525
+ final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
+ if ((targetSdk > VERSION_CODES.N_MR1) && (callback.networkRequest != null)) {
+ // http://b/20701525
+ throw new IllegalArgumentException("NetworkCallback already registered");
+ }
final NetworkRequest request;
try {
synchronized(sCallbacks) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 817cb5b..0610499 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -275,7 +275,7 @@
* Magic version number for a current development build, which has
* not yet turned into an official release.
*/
- public static final int CUR_DEVELOPMENT = 10000;
+ public static final int CUR_DEVELOPMENT = VMRuntime.SDK_VERSION_CUR_DEVELOPMENT;
/**
* October 2008: The original, first, version of Android. Yay!
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index b9e4bad..caea202 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -506,7 +506,7 @@
}
/**
- * Detect unbuffered input/output operations.
+ * Disable detection of unbuffered input/output operations.
*/
public Builder permitUnbufferedIo() {
return disable(DETECT_UNBUFFERED_IO);
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 78820b5..b525193 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -35,7 +35,6 @@
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
- public static final int PROP_NAME_MAX = 31;
public static final int PROP_VALUE_MAX = 91;
private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
@@ -82,12 +81,8 @@
/**
* Get the value for the given key.
* @return an empty string if the key isn't found
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
@@ -95,12 +90,8 @@
/**
* Get the value for the given key.
* @return if the key isn't found, return def if it isn't null, or an empty string otherwise
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key, String def) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
@@ -111,12 +102,8 @@
* @param def a default value to return
* @return the key parsed as an integer, or def if the key isn't found or
* cannot be parsed
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static int getInt(String key, int def) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_int(key, def);
}
@@ -127,12 +114,8 @@
* @param def a default value to return
* @return the key parsed as a long, or def if the key isn't found or
* cannot be parsed
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static long getLong(String key, long def) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_long(key, def);
}
@@ -148,25 +131,17 @@
* @param def a default value to return
* @return the key parsed as a boolean, or def if the key isn't found or is
* not able to be parsed as a boolean.
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static boolean getBoolean(String key, boolean def) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_boolean(key, def);
}
/**
* Set the value for the given key.
- * @throws IllegalArgumentException if the key exceeds 32 characters
* @throws IllegalArgumentException if the value exceeds 92 characters
*/
public static void set(String key, String val) {
- if (key.length() > PROP_NAME_MAX) {
- throw newKeyTooLargeException(key);
- }
if (val != null && val.length() > PROP_VALUE_MAX) {
throw newValueTooLargeException(key, val);
}
@@ -196,11 +171,6 @@
}
}
- private static IllegalArgumentException newKeyTooLargeException(String key) {
- return new IllegalArgumentException("system property key '" + key + "' is longer than "
- + PROP_NAME_MAX + " characters");
- }
-
private static IllegalArgumentException newValueTooLargeException(String key, String value) {
return new IllegalArgumentException("value of system property '" + key + "' is longer than "
+ PROP_VALUE_MAX + " characters: " + value);
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index bf03cce..8549cff 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -26,7 +26,18 @@
/**
* UpdateEngine handles calls to the update engine which takes care of A/B OTA
* updates. It wraps up the update engine Binder APIs and exposes them as
- * SystemApis, which will be called by system apps like GmsCore.
+ * SystemApis, which will be called by the system app responsible for OTAs.
+ * On a Google device, this will be GmsCore.
+ *
+ * The minimal flow is:
+ * <ol>
+ * <li>Create a new UpdateEngine instance.
+ * <li>Call {@link #bind}, optionally providing callbacks.
+ * <li>Call {@link #applyPayload}.
+ * </ol>
+ *
+ * In addition, methods are provided to {@link #cancel} or
+ * {@link #suspend}/{@link #resume} application of an update.
*
* The APIs defined in this class and UpdateEngineCallback class must be in
* sync with the ones in
@@ -80,12 +91,20 @@
private IUpdateEngine mUpdateEngine;
+ /**
+ * Creates a new instance.
+ */
@SystemApi
public UpdateEngine() {
mUpdateEngine = IUpdateEngine.Stub.asInterface(
ServiceManager.getService(UPDATE_ENGINE_SERVICE));
}
+ /**
+ * Prepares this instance for use. The callback will be notified on any
+ * status change, and when the update completes. A handler can be supplied
+ * to control which thread runs the callback, or null.
+ */
@SystemApi
public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
IUpdateEngineCallback updateEngineCallback = new IUpdateEngineCallback.Stub() {
@@ -125,11 +144,42 @@
}
}
+ /**
+ * Equivalent to {@code bind(callback, null)}.
+ */
@SystemApi
public boolean bind(final UpdateEngineCallback callback) {
return bind(callback, null);
}
+ /**
+ * Applies the payload found at the given {@code url}. For non-streaming
+ * updates, the URL can be a local file using the {@code file://} scheme.
+ *
+ * <p>The {@code offset} and {@code size} parameters specify the location
+ * of the payload within the file represented by the URL. This is useful
+ * if the downloadable package at the URL contains more than just the
+ * update_engine payload (such as extra metadata). This is true for
+ * Google's OTA system, where the URL points to a zip file in which the
+ * payload is stored uncompressed within the zip file alongside other
+ * data.
+ *
+ * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata
+ * to update_engine. In Google's implementation, this is stored as
+ * {@code payload_properties.txt} in the zip file. It's generated by the
+ * script {@code system/update_engine/scripts/brillo_update_payload}.
+ * The complete list of keys and their documentation is in
+ * {@code system/update_engine/common/constants.cc}, but an example
+ * might be:
+ * <pre>
+ * String[] pairs = {
+ * "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=",
+ * "FILE_SIZE=871903868",
+ * "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=",
+ * "METADATA_SIZE=70604"
+ * };
+ * </pre>
+ */
@SystemApi
public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
try {
@@ -139,6 +189,15 @@
}
}
+ /**
+ * Permanently cancels an in-progress update.
+ *
+ * <p>See {@link #resetStatus} to undo a finshed update (only available
+ * before the updated system has been rebooted).
+ *
+ * <p>See {@link #suspend} for a way to temporarily stop an in-progress
+ * update with the ability to resume it later.
+ */
@SystemApi
public void cancel() {
try {
@@ -148,6 +207,10 @@
}
}
+ /**
+ * Suspends an in-progress update. This can be undone by calling
+ * {@link #resume}.
+ */
@SystemApi
public void suspend() {
try {
@@ -157,6 +220,9 @@
}
}
+ /**
+ * Resumes a suspended update.
+ */
@SystemApi
public void resume() {
try {
@@ -166,6 +232,15 @@
}
}
+ /**
+ * Resets the bootable flag on the non-current partition and all internal
+ * update_engine state. This can be used after an unwanted payload has been
+ * successfully applied and the device has not yet been rebooted to signal
+ * that we no longer want to boot into that updated system. After this call
+ * completes, update_engine will no longer report
+ * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
+ * notification that rebooting into the new system is possible.
+ */
@SystemApi
public void resetStatus() {
try {
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index b3b856f..afff60a 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -19,7 +19,8 @@
import android.annotation.SystemApi;
/**
- * Callback function for UpdateEngine.
+ * Callback function for UpdateEngine. Used to keep the caller up to date
+ * with progress, so the UI (if any) can be updated.
*
* The APIs defined in this class and UpdateEngine class must be in sync with
* the ones in
@@ -31,9 +32,19 @@
@SystemApi
public abstract class UpdateEngineCallback {
+ /**
+ * Invoked when anything changes. The value of {@code status} will
+ * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
+ * and {@code percent} will be valid [TODO: in which cases?].
+ */
@SystemApi
public abstract void onStatusUpdate(int status, float percent);
+ /**
+ * Invoked when the payload has been applied, whether successfully or
+ * unsuccessfully. The value of {@code errorCode} will be one of the
+ * values from {@link UpdateEngine.ErrorCodeConstants}.
+ */
@SystemApi
public abstract void onPayloadApplicationComplete(int errorCode);
}
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 7b63b62..a21e05e 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -435,7 +435,7 @@
return null;
}
- final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 089eaec..62c5dca 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -410,11 +410,13 @@
/**
* Sets a {@link PreferenceDataStore} to be used by this Preference instead of using
* {@link android.content.SharedPreferences}.
- * <p>
- * The data store will remain assigned even if the Preference is moved between multiple
- * instances of {@link PreferenceFragment}.
+ *
+ * <p>The data store will remain assigned even if the Preference is moved around the preference
+ * hierarchy. It will also override a data store propagated from the {@link PreferenceManager}
+ * that owns this Preference.
*
* @param dataStore The {@link PreferenceDataStore} to be used by this Preference.
+ * @see PreferenceManager#setPreferenceDataStore(PreferenceDataStore)
*/
public void setPreferenceDataStore(PreferenceDataStore dataStore) {
mPreferenceDataStore = dataStore;
@@ -424,6 +426,12 @@
* Returns {@link PreferenceDataStore} used by this Preference. Returns {@code null} if
* {@link android.content.SharedPreferences} is used instead.
*
+ * <p>By default preferences always use {@link android.content.SharedPreferences}. To make this
+ * preference to use the {@link PreferenceDataStore} you need to assign your implementation
+ * to the Preference itself via {@link #setPreferenceDataStore(PreferenceDataStore)} or to its
+ * {@link PreferenceManager} via
+ * {@link PreferenceManager#setPreferenceDataStore(PreferenceDataStore)}.
+ *
* @return The {@link PreferenceDataStore} used by this Preference or {@code null} if none.
*/
@Nullable
@@ -1457,12 +1465,12 @@
}
/**
- * Implement this to set the initial value of the Preference.
+ * Implement this to set the initial value of the Preference.
* <p>
- * If <var>restorePersistedValue</var> is true, you should restore the
- * Preference value from the {@link android.content.SharedPreferences}. If
- * <var>restorePersistedValue</var> is false, you should set the Preference
- * value to defaultValue that is given (and possibly store to SharedPreferences
+ * If <var>restorePersistedValue</var> is true, you should restore the
+ * Preference value from the {@link android.content.SharedPreferences}. If
+ * <var>restorePersistedValue</var> is false, you should set the Preference
+ * value to defaultValue that is given (and possibly store to SharedPreferences
* if {@link #shouldPersist()} is true).
* <p>
* This may not always be called. One example is if it should not persist
diff --git a/core/java/android/preference/PreferenceDataStore.java b/core/java/android/preference/PreferenceDataStore.java
index e1a08ac..8caa404 100644
--- a/core/java/android/preference/PreferenceDataStore.java
+++ b/core/java/android/preference/PreferenceDataStore.java
@@ -21,16 +21,32 @@
import java.util.Set;
/**
- * A data store interface to be implemented and provided to the Preferences framework.
+ * A data store interface to be implemented and provided to the Preferences framework. This can be
+ * used to replace the default {@link android.content.SharedPreferences}, if needed.
*
- * Use this to replace the default {@link android.content.SharedPreferences}. By default, all "put"
- * methods throw {@link UnsupportedOperationException}.
+ * <p>In most cases you want to use {@link android.content.SharedPreferences} as it is automatically
+ * backed up and migrated to new devices. However, providing custom data store to preferences can be
+ * useful if your app stores its preferences in a local db, cloud or they are device specific like
+ * "Developer settings". It might be also useful when you want to use the preferences UI but
+ * the data are not supposed to be stored at all because they are valid per session only.
+ *
+ * <p>Once a put method is called it is full responsibility of the data store implementation to
+ * safely store the given values. Time expensive operations need to be done in the background to
+ * prevent from blocking the UI. You also need to have a plan on how to serialize the data in case
+ * the activity holding this object gets destroyed.
+ *
+ * <p>By default, all "put" methods throw {@link UnsupportedOperationException}.
+ *
+ * @see Preference#setPreferenceDataStore(PreferenceDataStore)
+ * @see PreferenceManager#setPreferenceDataStore(PreferenceDataStore)
*/
public interface PreferenceDataStore {
/**
* Set a String value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @see #getString(String, String)
@@ -42,6 +58,8 @@
/**
* Set a set of String value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param values The set of new values for the preference.
* @see #getStringSet(String, Set)
@@ -53,6 +71,8 @@
/**
* Set an int value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @see #getInt(String, int)
@@ -64,6 +84,8 @@
/**
* Set a long value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @see #getLong(String, long)
@@ -75,6 +97,8 @@
/**
* Set a float value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @see #getFloat(String, float)
@@ -86,6 +110,8 @@
/**
* Set a boolean value to the data store.
*
+ * <p>Once the value is set the data store is responsible for holding it.
+ *
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @see #getBoolean(String, boolean)
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 756c3f4..2570374 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -208,10 +208,13 @@
/**
* Sets a {@link PreferenceDataStore} to be used by all Preferences associated with this manager
- * that don't have a custom {@link PreferenceDataStore} assigned. Also if the data store is set,
- * the Preferences will no longer use {@link android.content.SharedPreferences}.
+ * that don't have a custom {@link PreferenceDataStore} assigned via
+ * {@link Preference#setPreferenceDataStore(PreferenceDataStore)}. Also if the data store is
+ * set, the child preferences won't use {@link android.content.SharedPreferences} as long as
+ * they are assigned to this manager.
*
* @param dataStore The {@link PreferenceDataStore} to be used by this manager.
+ * @see Preference#setPreferenceDataStore(PreferenceDataStore)
*/
public void setPreferenceDataStore(PreferenceDataStore dataStore) {
mPreferenceDataStore = dataStore;
@@ -219,9 +222,10 @@
/**
* Returns the {@link PreferenceDataStore} associated with this manager or {@code null} if
- * {@link android.content.SharedPreferences} are used instead.
+ * the default {@link android.content.SharedPreferences} are used instead.
*
* @return The {@link PreferenceDataStore} associated with this manager or {@code null} if none.
+ * @see #setPreferenceDataStore(PreferenceDataStore)
*/
@Nullable
public PreferenceDataStore getPreferenceDataStore() {
@@ -358,8 +362,11 @@
* Sets the name of the SharedPreferences file that preferences managed by this
* will use.
*
+ * <p>If custom {@link PreferenceDataStore} is set, this won't override its usage.
+ *
* @param sharedPreferencesName The name of the SharedPreferences file.
* @see Context#getSharedPreferences(String, int)
+ * @see #setPreferenceDataStore(PreferenceDataStore)
*/
public void setSharedPreferencesName(String sharedPreferencesName) {
mSharedPreferencesName = sharedPreferencesName;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 5486162..33b5903 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -119,6 +119,9 @@
* <p>Location should specify a document URI or a tree URI with document ID. If
* this URI identifies a non-directory, document navigator will attempt to use the parent
* of the document as the initial location.
+ *
+ * <p>The initial location is system specific if this extra is missing or document navigator
+ * failed to locate the desired initial location.
*/
public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
@@ -162,9 +165,6 @@
public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
/** {@hide} */
- public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
-
- /** {@hide} */
public static final String
ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 5461e6b..ad8ad69 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -138,8 +138,6 @@
public static final int KM_PURPOSE_DECRYPT = 1;
public static final int KM_PURPOSE_SIGN = 2;
public static final int KM_PURPOSE_VERIFY = 3;
- public static final int KM_PURPOSE_DERIVE_KEY = 4;
- public static final int KM_PURPOSE_WRAP_KEY = 5;
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 4099f59..3c211eb 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -15,9 +15,12 @@
*/
package android.service.autofill;
+import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Message;
import android.os.RemoteException;
+import android.view.accessibility.AccessibilityInteractionClient;
import com.android.internal.os.HandlerCaller;
import android.annotation.SdkConstant;
import android.app.Activity;
@@ -79,6 +82,15 @@
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
+ public void onInit(IAutoFillServiceConnection connection) {
+ if (connection != null) {
+ mHandlerCaller.obtainMessageO(MSG_CONNECT, connection).sendToTarget();
+ } else {
+ mHandlerCaller.obtainMessage(MSG_DISCONNECT).sendToTarget();
+ }
+ }
+
+ @Override
public void onFillRequest(AssistStructure structure, Bundle extras,
IFillCallback callback) {
ICancellationSignal transport = CancellationSignal.createTransport();
@@ -98,21 +110,12 @@
mHandlerCaller.obtainMessageOOO(MSG_ON_SAVE_REQUEST, structure,
extras, callback).sendToTarget();
}
-
- @Override
- public void onConnected() {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT));
- }
-
- @Override
- public void onDisconnected() {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DISCONNECT));
- }
};
private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
switch (msg.what) {
case MSG_CONNECT: {
+ mConnection = (IAutoFillServiceConnection) msg.obj;
onConnected();
break;
} case MSG_ON_FILL_REQUEST: {
@@ -136,6 +139,7 @@
break;
} case MSG_DISCONNECT: {
onDisconnected();
+ mConnection = null;
break;
} default: {
Log.w(TAG, "MyCallbacks received invalid message type: " + msg);
@@ -145,6 +149,8 @@
private HandlerCaller mHandlerCaller;
+ private IAutoFillServiceConnection mConnection;
+
/**
* {@inheritDoc}
*
@@ -223,4 +229,22 @@
public void onDisconnected() {
//TODO(b/33197203): is not called anymore, fix it!
}
+
+ /**
+ * Disables the service. After calling this method, the service will
+ * be disabled and settings will show that it is turned off.
+ *
+ * <p>You should call this method only after a call to {@link #onConnected()}
+ * and before the corresponding call to {@link #onDisconnected()}. In other words
+ * you can disable your service only while the system is connected to it.</p>
+ */
+ public final void disableSelf() {
+ if (mConnection != null) {
+ try {
+ mConnection.disableSelf();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index ba75c8b..ef551ad 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -171,7 +171,7 @@
if (false) {
// TODO(b/33197203, 35727295): this is how mSaveInfo will be set once we don't support
- // FillResponse.setSavableIds()
+ // FillResponse.addSavableIds()
mSaveInfo = builder.mSaveInfo;
if (mSaveInfo != null) {
mSaveInfo.addSavableIds(mDatasets);
@@ -181,11 +181,11 @@
}
}
} else {
- // Temporary workaround to support FillResponse.setSavableIds()
+ // Temporary workaround to support FillResponse.addSavableIds()
SaveInfo saveInfo = builder.mSaveInfoBuilder != null ? builder.mSaveInfoBuilder.build()
: builder.mSaveInfo;
- // Handle the the case where service didn't call setSavableIds() because it would
+ // Handle the the case where service didn't call addSavableIds() because it would
// contain just the ids from the datasets.
if (saveInfo == null && mDatasets != null) {
saveInfo = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC).build();
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index fa1ea65..80685d8 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -18,6 +18,7 @@
import android.app.assist.AssistStructure;
import android.os.Bundle;
+import android.service.autofill.IAutoFillServiceConnection;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import com.android.internal.os.IResultReceiver;
@@ -28,10 +29,9 @@
* @hide
*/
oneway interface IAutoFillService {
+ void onInit(in IAutoFillServiceConnection connection);
void onFillRequest(in AssistStructure structure, in Bundle extras,
in IFillCallback callback);
void onSaveRequest(in AssistStructure structure, in Bundle extras,
in ISaveCallback callback);
- void onConnected();
- void onDisconnected();
}
diff --git a/core/java/android/service/autofill/IAutoFillServiceConnection.aidl b/core/java/android/service/autofill/IAutoFillServiceConnection.aidl
new file mode 100644
index 0000000..cf73a10
--- /dev/null
+++ b/core/java/android/service/autofill/IAutoFillServiceConnection.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+/**
+ * Interface from an auto fill service to the system.
+ *
+ * @hide
+ */
+interface IAutoFillServiceConnection {
+ void disableSelf();
+}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 096f28b..a8f9aee 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -72,6 +73,8 @@
public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
private final @SaveDataType int mType;
+ private CharSequence mNegativeActionTitle;
+ private IntentSender mNegativeActionListener;
private ArraySet<AutoFillId> mSavableIds;
private final CharSequence mDescription;
@@ -88,11 +91,23 @@
private SaveInfo(Builder builder) {
mType = builder.mType;
+ mNegativeActionTitle = builder.mNegativeActionTitle;
+ mNegativeActionListener = builder.mNegativeActionListener;
mSavableIds = builder.mSavableIds;
mDescription = builder.mDescription;
}
/** @hide */
+ public @Nullable CharSequence getNegativeActionTitle() {
+ return mNegativeActionTitle;
+ }
+
+ /** @hide */
+ public @Nullable IntentSender getNegativeActionListener() {
+ return mNegativeActionListener;
+ }
+
+ /** @hide */
public @Nullable ArraySet<AutoFillId> getSavableIds() {
return mSavableIds;
}
@@ -132,6 +147,8 @@
public static final class Builder {
private final @SaveDataType int mType;
+ private CharSequence mNegativeActionTitle;
+ private IntentSender mNegativeActionListener;
private ArraySet<AutoFillId> mSavableIds;
private CharSequence mDescription;
private boolean mDestroyed;
@@ -195,6 +212,42 @@
}
/**
+ * Sets the title and listener for the negative save action.
+ *
+ * <p>This allows a fill-provider to customize the text and be
+ * notified when the user selects the negative action in the save
+ * UI. Note that selecting the negative action regardless of its text
+ * and listener being customized would dismiss the save UI and if a
+ * custom listener intent is provided then this intent will be
+ * started.</p>
+ *
+ * <p>This customization could be useful for providing additional
+ * semantics to the negative action. For example, a fill-provider
+ * can use this mechanism to add a "Disable" function or a "More info"
+ * function, etc. Note that the save action is exclusively controlled
+ * by the platform to ensure user consent is collected to release
+ * data from the filled app to the fill-provider.</p>
+ *
+ * @param title The action title.
+ * @param listener The action listener.
+ * @return This builder.
+ *
+ * @throws IllegalArgumentException If the title and the listener
+ * are not both either null or non-null.
+ */
+ public @NonNull Builder setNegativeAction(@Nullable CharSequence title,
+ @Nullable IntentSender listener) {
+ throwIfDestroyed();
+ if (title == null ^ listener == null) {
+ throw new IllegalArgumentException("title and listener"
+ + " must be both non-null or null");
+ }
+ mNegativeActionTitle = title;
+ mNegativeActionListener = listener;
+ return this;
+ }
+
+ /**
* Builds a new {@link SaveInfo} instance.
*/
public SaveInfo build() {
@@ -235,6 +288,8 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mType);
+ parcel.writeCharSequence(mNegativeActionTitle);
+ parcel.writeParcelable(mNegativeActionListener, flags);
parcel.writeTypedArraySet(mSavableIds, flags);
parcel.writeCharSequence(mDescription);
}
@@ -246,6 +301,7 @@
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
final Builder builder = new Builder(parcel.readInt());
+ builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
final ArraySet<AutoFillId> savableIds = parcel.readTypedArraySet(null);
final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0;
for (int i = 0; i < savableIdsCount; i++) {
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 1781c2a..2116847 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -126,7 +126,9 @@
public static final String EXTRA_TOKEN = "token";
/**
- * @hide
+ * May be included when {@link #ACTION_QS_TILE_PREFERENCES} is launched from a tile.
+ * This extra may contain the {@link ComponentName} of the tile that triggered
+ * the preferences to be opened.
*/
public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index c3d3f39..16d4666 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -94,6 +94,7 @@
}
}
};
+ private boolean mAcceptAllTouches;
public NotificationHeaderView(Context context) {
this(context, null);
@@ -374,6 +375,8 @@
case MotionEvent.ACTION_DOWN:
mTrackGesture = false;
if (isInside(x, y)) {
+ mDownX = x;
+ mDownY = y;
mTrackGesture = true;
return true;
}
@@ -396,11 +399,12 @@
}
private boolean isInside(float x, float y) {
+ if (mAcceptAllTouches) {
+ return true;
+ }
for (int i = 0; i < mTouchRects.size(); i++) {
Rect r = mTouchRects.get(i);
if (r.contains((int) x, (int) y)) {
- mDownX = x;
- mDownY = y;
return true;
}
}
@@ -433,4 +437,9 @@
}
return mTouchListener.isInside(x, y);
}
+
+ @RemotableViewMethod
+ public void setAcceptAllTouches(boolean acceptAllTouches) {
+ mAcceptAllTouches = acceptAllTouches;
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0ac16c1..b718696 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -95,11 +95,6 @@
IBinder displayToken, int mode);
private static native void nativeDeferTransactionUntil(long nativeObject,
IBinder handle, long frame);
- private static native void nativeDeferTransactionUntilSurface(long nativeObject,
- long surfaceObject, long frame);
- private static native void nativeReparentChildren(long nativeObject,
- IBinder handle);
- private static native void nativeSeverChildren(long nativeObject);
private static native void nativeSetOverrideScalingMode(long nativeObject,
int scalingMode);
private static native IBinder nativeGetHandle(long nativeObject);
@@ -426,18 +421,6 @@
nativeDeferTransactionUntil(mNativeObject, handle, frame);
}
- public void deferTransactionUntil(Surface barrier, long frame) {
- nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
- }
-
- public void reparentChildren(IBinder newParentHandle) {
- nativeReparentChildren(mNativeObject, newParentHandle);
- }
-
- public void detachChildren() {
- nativeSeverChildren(mNativeObject);
- }
-
public void setOverrideScalingMode(int scalingMode) {
checkNotReleased();
nativeSetOverrideScalingMode(mNativeObject, scalingMode);
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index b5912bc..3cf5af4 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -27,7 +27,6 @@
private long mNativeClient; // SurfaceComposerClient*
private static native long nativeCreate();
- private static native long nativeCreateScoped(long surfacePtr);
private static native void nativeDestroy(long ptr);
private static native void nativeKill(long ptr);
@@ -36,10 +35,6 @@
mNativeClient = nativeCreate();
}
- public SurfaceSession(Surface root) {
- mNativeClient = nativeCreateScoped(root.mNativeObject);
- }
-
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 6430633..d2577d4 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,10 +16,6 @@
package android.view;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
-
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
import android.content.res.Configuration;
@@ -30,12 +26,16 @@
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
+import com.android.internal.view.BaseIWindow;
import com.android.internal.view.SurfaceCallbackHelper;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
@@ -92,8 +92,8 @@
* positioned asynchronously.</p>
*/
public class SurfaceView extends View {
- private static final String TAG = "SurfaceView";
- private static final boolean DEBUG = false;
+ static private final String TAG = "SurfaceView";
+ static private final boolean DEBUG = false;
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
@@ -102,23 +102,28 @@
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface(); // Current surface in use
+ final Surface mNewSurface = new Surface(); // New surface we are switching to
boolean mDrawingStopped = true;
- // We use this to track if the application has produced a frame
- // in to the Surface. Up until that point, we should be careful not to punch
- // holes.
- boolean mDrawFinished = false;
- final Rect mScreenRect = new Rect();
- SurfaceSession mSurfaceSession;
-
- SurfaceControl mSurfaceControl;
+ final WindowManager.LayoutParams mLayout
+ = new WindowManager.LayoutParams();
+ IWindowSession mSession;
+ MyWindow mWindow;
+ final Rect mVisibleInsets = new Rect();
+ final Rect mWinFrame = new Rect();
+ final Rect mOverscanInsets = new Rect();
+ final Rect mContentInsets = new Rect();
+ final Rect mStableInsets = new Rect();
+ final Rect mOutsets = new Rect();
+ final Rect mBackdropFrame = new Rect();
final Rect mTmpRect = new Rect();
final Configuration mConfiguration = new Configuration();
static final int KEEP_SCREEN_ON_MSG = 1;
- static final int DRAW_FINISHED_MSG = 2;
+ static final int GET_NEW_SURFACE_MSG = 2;
+ static final int UPDATE_WINDOW_MSG = 3;
- int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
boolean mIsCreating = false;
private volatile boolean mRtHandlingPositionUpdates = false;
@@ -130,9 +135,11 @@
case KEEP_SCREEN_ON_MSG: {
setKeepScreenOn(msg.arg1 != 0);
} break;
- case DRAW_FINISHED_MSG: {
- mDrawFinished = true;
- invalidate();
+ case GET_NEW_SURFACE_MSG: {
+ handleGetNewSurface();
+ } break;
+ case UPDATE_WINDOW_MSG: {
+ updateWindow();
} break;
}
}
@@ -142,7 +149,7 @@
= new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
- updateSurface();
+ updateWindow();
}
};
@@ -152,14 +159,13 @@
public boolean onPreDraw() {
// reposition ourselves where the surface is
mHaveFrame = getWidth() > 0 && getHeight() > 0;
- updateSurface();
+ updateWindow();
return true;
}
};
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
- boolean mLastWindowVisibility = false;
boolean mViewVisibility = false;
int mRequestedWidth = -1;
int mRequestedHeight = -1;
@@ -175,17 +181,19 @@
boolean mVisible = false;
int mWindowSpaceLeft = -1;
int mWindowSpaceTop = -1;
- int mSurfaceWidth = -1;
- int mSurfaceHeight = -1;
+ int mWindowSpaceWidth = -1;
+ int mWindowSpaceHeight = -1;
int mFormat = -1;
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+ boolean mUpdateWindowNeeded;
+ boolean mReportDrawNeeded;
private Translator mTranslator;
+ private int mWindowInsetLeft;
+ private int mWindowInsetTop;
private boolean mGlobalListenersAdded;
- private int mSurfaceFlags = SurfaceControl.HIDDEN;
-
public SurfaceView(Context context) {
this(context, null);
}
@@ -219,8 +227,11 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);
+ mSession = getWindowSession();
+ mLayout.token = getWindowToken();
+ mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
+ mLayout.packageName = mContext.getOpPackageName();
mViewVisibility = getVisibility() == VISIBLE;
- mRequestedVisible = mViewVisibility && mWindowVisibility;
if (!mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
@@ -235,7 +246,7 @@
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
- updateSurface();
+ updateWindow();
}
@Override
@@ -253,7 +264,7 @@
requestLayout();
}
mRequestedVisible = newRequestedVisible;
- updateSurface();
+ updateWindow();
}
@Override
@@ -266,14 +277,19 @@
}
mRequestedVisible = false;
-
- updateSurface();
- if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
- }
- mSurfaceControl = null;
-
+ updateWindow();
mHaveFrame = false;
+ if (mWindow != null) {
+ try {
+ mSession.remove(mWindow);
+ } catch (RemoteException ex) {
+ // Not much we can do here...
+ }
+ mWindow = null;
+ }
+ mSession = null;
+ mLayout.token = null;
+
super.onDetachedFromWindow();
}
@@ -292,13 +308,13 @@
@Override
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean result = super.setFrame(left, top, right, bottom);
- updateSurface();
+ updateWindow();
return result;
}
@Override
public boolean gatherTransparentRegion(Region region) {
- if (isAboveParent()) {
+ if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
return super.gatherTransparentRegion(region);
}
@@ -325,7 +341,7 @@
@Override
public void draw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
+ if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
@@ -337,8 +353,8 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
- // draw() is not called when SKIP_DRAW is set
+ if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ // if SKIP_DRAW is cleared, draw() has already punched a hole
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -359,8 +375,9 @@
* <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
*/
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
- mSubLayer = isMediaOverlay
- ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
+ mWindowType = isMediaOverlay
+ ? WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
+ : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
}
/**
@@ -378,9 +395,12 @@
*/
public void setZOrderOnTop(boolean onTop) {
if (onTop) {
- mSubLayer = APPLICATION_PANEL_SUBLAYER;
+ mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ // ensures the surface is placed below the IME
+ mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
- mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
}
@@ -398,23 +418,31 @@
*/
public void setSecure(boolean isSecure) {
if (isSecure) {
- mSurfaceFlags |= SurfaceControl.SECURE;
+ mLayout.flags |= WindowManager.LayoutParams.FLAG_SECURE;
} else {
- mSurfaceFlags &= ~SurfaceControl.SECURE;
+ mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
}
}
+ /**
+ * Hack to allow special layering of windows. The type is one of the
+ * types in WindowManager.LayoutParams. This is a hack so:
+ * @hide
+ */
+ public void setWindowType(int type) {
+ mWindowType = type;
+ }
+
/** @hide */
- protected void updateSurface() {
+ protected void updateWindow() {
if (!mHaveFrame) {
return;
}
ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
- return;
+ if (viewRoot != null) {
+ mTranslator = viewRoot.mTranslator;
}
- mTranslator = viewRoot.mTranslator;
if (mTranslator != null) {
mSurface.setCompatibilityTranslator(mTranslator);
}
@@ -424,15 +452,17 @@
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
+ final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
- final boolean creating = (mSurfaceControl == null || formatChanged)
- && mRequestedVisible;
- final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+ final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
- final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+ final boolean layoutSizeChanged = getWidth() != mLayout.width
+ || getHeight() != mLayout.height;
+
boolean redrawNeeded = false;
- if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+ if (creating || formatChanged || sizeChanged || visibleChanged
+ || mUpdateWindowNeeded || mReportDrawNeeded) {
getLocationInWindow(mLocation);
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
@@ -446,74 +476,93 @@
final boolean visible = mVisible = mRequestedVisible;
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
+ mWindowSpaceWidth = myWidth;
+ mWindowSpaceHeight = myHeight;
mFormat = mRequestedFormat;
- mLastWindowVisibility = mWindowVisibility;
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ // Scaling/Translate window's layout here because mLayout is not used elsewhere.
+
+ // Places the window relative
+ mLayout.x = mWindowSpaceLeft;
+ mLayout.y = mWindowSpaceTop;
+ mLayout.width = getWidth();
+ mLayout.height = getHeight();
if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
}
- if (creating) {
- mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
- mSurfaceControl = new SurfaceControl(mSurfaceSession,
- "SurfaceView - " + viewRoot.getTitle().toString(),
- mSurfaceWidth, mSurfaceHeight, mFormat,
- mSurfaceFlags);
+ mLayout.format = mRequestedFormat;
+ mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | WindowManager.LayoutParams.FLAG_SCALED
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ ;
+ if (!creating && !sizeChanged) {
+ mLayout.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
+ } else {
+ mLayout.privateFlags &=
+ ~WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
}
- boolean realSizeChanged = false;
+ if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
+ mLayout.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+ mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+
+ if (mWindow == null) {
+ Display display = getDisplay();
+ mWindow = new MyWindow(this);
+ mLayout.type = mWindowType;
+ mLayout.gravity = Gravity.START|Gravity.TOP;
+ mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
+ mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets,
+ mStableInsets);
+ }
+
+ boolean realSizeChanged;
+ boolean reportDrawNeeded;
+
+ int relayoutResult;
mSurfaceLock.lock();
try {
+ mUpdateWindowNeeded = false;
+ reportDrawNeeded = mReportDrawNeeded;
+ mReportDrawNeeded = false;
mDrawingStopped = !visible;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
- SurfaceControl.openTransaction();
- try {
- mSurfaceControl.setLayer(mSubLayer);
- if (mViewVisibility) {
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
-
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- if (creating || !mRtHandlingPositionUpdates) {
- mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
- mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- mScreenRect.height() / (float) mSurfaceHeight);
- }
- if (sizeChanged) {
- mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
- }
- } finally {
- SurfaceControl.closeTransaction();
+ relayoutResult = mSession.relayout(
+ mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight,
+ visible ? VISIBLE : GONE,
+ WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
+ mWinFrame, mOverscanInsets, mContentInsets,
+ mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
+ mConfiguration, mNewSurface);
+ if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+ reportDrawNeeded = true;
}
- if (sizeChanged || creating) {
- redrawNeeded = true;
- }
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "New surface: " + mNewSurface
+ + ", vis=" + visible + ", frame=" + mWinFrame);
mSurfaceFrame.left = 0;
mSurfaceFrame.top = 0;
if (mTranslator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
+ mSurfaceFrame.right = mWinFrame.width();
+ mSurfaceFrame.bottom = mWinFrame.height();
} else {
float appInvertedScale = mTranslator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
}
final int surfaceWidth = mSurfaceFrame.right;
@@ -527,11 +576,12 @@
}
try {
- redrawNeeded |= visible && !mDrawFinished;
+ redrawNeeded |= creating | reportDrawNeeded;
SurfaceHolder.Callback callbacks[] = null;
- final boolean surfaceChanged = creating;
+ final boolean surfaceChanged = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
@@ -558,10 +608,7 @@
}
}
- if (creating) {
- mSurface.copyFrom(mSurfaceControl);
- }
-
+ mSurface.transferFrom(mNewSurface);
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
@@ -594,57 +641,53 @@
callbacks = getSurfaceCallbacks();
}
SurfaceCallbackHelper sch =
- new SurfaceCallbackHelper(this::onDrawFinished);
+ new SurfaceCallbackHelper(mSession, mWindow);
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
}
}
} finally {
mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- mSurfaceControl.destroy();
- mSurfaceControl = null;
- }
+ mSession.performDeferredDestroy(mWindow);
}
- } catch (Exception ex) {
+ } catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
}
if (DEBUG) Log.v(
- TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
- + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
- + ", frame=" + mSurfaceFrame);
+ TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
+ " w=" + mLayout.width + " h=" + mLayout.height +
+ ", frame=" + mSurfaceFrame);
} else {
// Calculate the window position in case RT loses the window
// and we need to fallback to a UI-thread driven position update
getLocationInWindow(mLocation);
final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
|| mWindowSpaceTop != mLocation[1];
- final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
- || getHeight() != mScreenRect.height();
if (positionChanged || layoutSizeChanged) { // Only the position has changed
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
- // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+ // For our size changed check, we keep mLayout.width and mLayout.height
// in view local space.
- mLocation[0] = getWidth();
- mLocation[1] = getHeight();
+ mLocation[0] = mLayout.width = getWidth();
+ mLocation[1] = mLayout.height = getHeight();
transformFromViewToWindowSpace(mLocation);
- mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mTmpRect.set(mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1]);
if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ mTranslator.translateRectInAppWindowToScreen(mTmpRect);
}
if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+ if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition UI, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, -1);
- } catch (Exception ex) {
+ mTmpRect.left, mTmpRect.top,
+ mTmpRect.right, mTmpRect.bottom));
+ mSession.repositionChild(mWindow, mTmpRect.left, mTmpRect.top,
+ mTmpRect.right, mTmpRect.bottom, -1, mTmpRect);
+ } catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
}
}
@@ -652,40 +695,18 @@
}
}
- private void onDrawFinished() {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " "
- + "finishedDrawing");
- }
- mHandler.sendEmptyMessage(DRAW_FINISHED_MSG);
- }
-
- private void setParentSpaceRectangle(Rect position, long frameNumber) {
- ViewRootImpl viewRoot = getViewRootImpl();
-
- SurfaceControl.openTransaction();
- try {
- if (frameNumber > 0) {
- mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
- }
- mSurfaceControl.setPosition(position.left, position.top);
- mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- position.height() / (float) mSurfaceHeight);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
private Rect mRTLastReportedPosition = new Rect();
/**
* Called by native by a Rendering Worker thread to update the window position
* @hide
*/
- public final void updateSurfacePosition_renderWorker(long frameNumber,
+ public final void updateWindowPosition_renderWorker(long frameNumber,
int left, int top, int right, int bottom) {
- if (mSurfaceControl == null) {
+ IWindowSession session = mSession;
+ MyWindow window = mWindow;
+ if (session == null || window == null) {
+ // Guess we got detached, that sucks
return;
}
// TODO: This is teensy bit racey in that a brand new SurfaceView moving on
@@ -705,29 +726,35 @@
}
try {
if (DEBUG) {
- Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+ Log.d(TAG, String.format("%d updateWindowPosition RenderWorker, frameNr = %d, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
frameNumber, left, top, right, bottom));
}
- mRTLastReportedPosition.set(left, top, right, bottom);
- setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Just using mRTLastReportedPosition as a dummy rect here
+ session.repositionChild(window, left, top, right, bottom,
+ frameNumber,
+ mRTLastReportedPosition);
// Now overwrite mRTLastReportedPosition with our values
- } catch (Exception ex) {
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ } catch (RemoteException ex) {
Log.e(TAG, "Exception from repositionChild", ex);
}
}
/**
- * Called by native on RenderThread to notify that the view is no longer in the
+ * Called by native on RenderThread to notify that the window is no longer in the
* draw tree. UI thread is blocked at this point.
* @hide
*/
- public final void surfacePositionLost_uiRtSync(long frameNumber) {
+ public final void windowPositionLost_uiRtSync(long frameNumber) {
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
}
- if (mSurfaceControl == null) {
+ IWindowSession session = mSession;
+ MyWindow window = mWindow;
+ if (session == null || window == null) {
+ // We got detached prior to receiving this, abort
return;
}
if (mRtHandlingPositionUpdates) {
@@ -736,14 +763,19 @@
// safely access other member variables at this time.
// So do what the UI thread would have done if RT wasn't handling position
// updates.
- if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+ mTmpRect.set(mLayout.x, mLayout.y,
+ mLayout.x + mLayout.width,
+ mLayout.y + mLayout.height);
+
+ if (!mTmpRect.isEmpty() && !mTmpRect.equals(mRTLastReportedPosition)) {
try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+ if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, frameNumber);
- } catch (Exception ex) {
+ mTmpRect.left, mTmpRect.top,
+ mTmpRect.right, mTmpRect.bottom));
+ session.repositionChild(window, mTmpRect.left, mTmpRect.top,
+ mTmpRect.right, mTmpRect.bottom, frameNumber, mWinFrame);
+ } catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
}
}
@@ -760,6 +792,10 @@
return callbacks;
}
+ void handleGetNewSurface() {
+ updateWindow();
+ }
+
/**
* Check to see if the surface has fixed size dimensions or if the surface's
* dimensions are dimensions are dependent on its current layout.
@@ -771,8 +807,65 @@
return (mRequestedWidth != -1 || mRequestedHeight != -1);
}
- private boolean isAboveParent() {
- return mSubLayer >= 0;
+ private static class MyWindow extends BaseIWindow {
+ private final WeakReference<SurfaceView> mSurfaceView;
+
+ public MyWindow(SurfaceView surfaceView) {
+ mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
+ }
+
+ @Override
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
+ Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+ Configuration newConfig, Rect backDropRect, boolean forceLayout,
+ boolean alwaysConsumeNavBar, int displayId) {
+ SurfaceView surfaceView = mSurfaceView.get();
+ if (surfaceView != null) {
+ if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width()
+ + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight);
+ surfaceView.mSurfaceLock.lock();
+ try {
+ if (reportDraw) {
+ surfaceView.mUpdateWindowNeeded = true;
+ surfaceView.mReportDrawNeeded = true;
+ surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
+ } else if (surfaceView.mWinFrame.width() != frame.width()
+ || surfaceView.mWinFrame.height() != frame.height()
+ || forceLayout) {
+ surfaceView.mUpdateWindowNeeded = true;
+ surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
+ }
+ } finally {
+ surfaceView.mSurfaceLock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public void dispatchAppVisibility(boolean visible) {
+ // The point of SurfaceView is to let the app control the surface.
+ }
+
+ @Override
+ public void dispatchGetNewSurface() {
+ SurfaceView surfaceView = mSurfaceView.get();
+ if (surfaceView != null) {
+ Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
+ surfaceView.mHandler.sendMessage(msg);
+ }
+ }
+
+ @Override
+ public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
+ Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
+ }
+
+ @Override
+ public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
+ }
+
+ int mCurWidth = -1;
+ int mCurHeight = -1;
}
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@@ -820,14 +913,15 @@
@Override
public void setFormat(int format) {
+
// for backward compatibility reason, OPAQUE always
// means 565 for SurfaceView
if (format == PixelFormat.OPAQUE)
format = PixelFormat.RGB_565;
mRequestedFormat = format;
- if (mSurfaceControl != null) {
- updateSurface();
+ if (mWindow != null) {
+ updateWindow();
}
}
@@ -888,10 +982,10 @@
mSurfaceLock.lock();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
- + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+ + mDrawingStopped + ", win=" + mWindow);
Canvas c = null;
- if (!mDrawingStopped && mSurfaceControl != null) {
+ if (!mDrawingStopped && mWindow != null) {
try {
if (hardware) {
c = mSurface.lockHardwareCanvas();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e349170..99051a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -974,6 +974,143 @@
*/
public static final int AUTO_FILL_MODE_MANUAL = 2;
+ /** @hide */
+ @IntDef({
+ AUTO_FILL_HINT_NONE,
+ AUTO_FILL_HINT_EMAIL_ADDRESS,
+ AUTO_FILL_HINT_NAME,
+ AUTO_FILL_HINT_POSTAL_ADDRESS,
+ AUTO_FILL_HINT_PASSWORD,
+ AUTO_FILL_HINT_PHONE,
+ AUTO_FILL_HINT_USERNAME,
+ AUTO_FILL_HINT_POSTAL_CODE,
+ AUTO_FILL_HINT_CREDIT_CARD_NUMBER,
+ AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutoFillHint {}
+
+ /**
+ * No auto-fill hint is set.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_NONE = 0;
+
+ /**
+ * This view contains an email address.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 0x1;
+
+ /**
+ * The view contains a real name.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_NAME = 0x2;
+
+ /**
+ * The view contains a user name.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_USERNAME = 0x4;
+
+ /**
+ * The view contains a password.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_PASSWORD = 0x8;
+
+ /**
+ * The view contains a phone number.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_PHONE = 0x10;
+
+ /**
+ * The view contains a postal address.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 0x20;
+
+ /**
+ * The view contains a postal code.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_POSTAL_CODE = 0x40;
+
+ /**
+ * The view contains a credit card number.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 0x80;
+
+ /**
+ * The view contains a credit card security code.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100;
+
+ /**
+ * The view contains a credit card expiration date.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200;
+
+ /**
+ * The view contains the month a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400;
+
+ /**
+ * The view contains the year a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800;
+
+ /**
+ * The view contains the day a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000;
+
+ /**
+ * Hint for the auto-fill services that describes the content of the view.
+ */
+ @AutoFillHint private int mAutoFillHint;
+
/**
* This view is enabled. Interpretation varies by subclass.
* Use with ENABLED_MASK when calling setFlags.
@@ -4799,11 +4936,16 @@
setFocusedByDefault(a.getBoolean(attr, true));
}
break;
- case com.android.internal.R.styleable.View_autoFillMode:
+ case R.styleable.View_autoFillMode:
if (a.peekValue(attr) != null) {
setAutoFillMode(a.getInt(attr, AUTO_FILL_MODE_INHERIT));
}
break;
+ case R.styleable.View_autoFillHint:
+ if (a.peekValue(attr) != null) {
+ setAutoFillHint(a.getInt(attr, AUTO_FILL_HINT_NONE));
+ }
+ break;
}
}
@@ -7035,6 +7177,7 @@
// to reuse the accessibility id to save space.
structure.setAutoFillId(getAccessibilityViewId());
structure.setAutoFillType(autoFillType);
+ structure.setAutoFillHint(getAutoFillHint());
structure.setAutoFillValue(getAutoFillValue());
}
}
@@ -7178,7 +7321,7 @@
/**
* Describes the auto-fill type that should be used on calls to
* {@link #autoFill(AutoFillValue)} and {@link #autoFillVirtual(int, AutoFillValue)}.
-
+ *
* <p>By default returns {@code null}, but views should override it (and
* {@link #autoFill(AutoFillValue)} to support the AutoFill Framework.
*/
@@ -7188,9 +7331,21 @@
}
/**
+ * Describes the content of a view so that a auto-fill service can fill in the appropriate data.
+ *
+ * @return The hint set via the attribute
+ *
+ * @attr ref android.R.styleable#View_autoFillHint
+ */
+ @ViewDebug.ExportedProperty()
+ @AutoFillHint public int getAutoFillHint() {
+ return mAutoFillHint;
+ }
+
+ /**
* Gets the {@link View}'s current auto-fill value.
*
- * <p>By default returns {@code null}, but views should override it,
+ * <p>By default returns {@code null}, but views should override it (and
* {@link #autoFill(AutoFillValue)}, and {@link #getAutoFillType()} to support the AutoFill
* Framework.
*/
@@ -8698,6 +8853,17 @@
}
/**
+ * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+ * view.
+ *
+ * @param autoFillHint The auto-fill hint to set
+ * @attr ref android.R.styleable#View_autoFillHint
+ */
+ public void setAutoFillHint(@AutoFillHint int autoFillHint) {
+ mAutoFillHint = autoFillHint;
+ }
+
+ /**
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f9863b0..20d960f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2632,14 +2632,6 @@
}
}
- private void onDrawFinished() {
- try {
- mWindowSession.finishDrawing(mWindow);
- } catch (RemoteException e) {
- // Have fun!
- }
- }
-
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
@@ -2690,7 +2682,7 @@
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::onDrawFinished);
+ SurfaceCallbackHelper sch = new SurfaceCallbackHelper(mWindowSession, mWindow);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 84c2c84..bc0960c 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -301,6 +301,13 @@
public abstract void setAutoFillType(AutoFillType info);
/**
+ * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+ * view.
+ */
+ // TODO(b/35364993): add CTS/unit test
+ public abstract void setAutoFillHint(@View.AutoFillHint int hint);
+
+ /**
* Sets the {@link AutoFillValue} representing the current value of this node.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
@@ -316,6 +323,13 @@
public abstract void setAutoFillOptions(String[] options);
/**
+ * Sets the {@link android.text.InputType} bits of this node.
+ *
+ * @param inputType inputType bits as defined by {@link android.text.InputType}.
+ */
+ public abstract void setInputType(int inputType);
+
+ /**
* Marks this node as sanitized so its content are sent on {@link
* android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
* Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java
index 5d85bfd..62913d8 100644
--- a/core/java/android/view/autofill/AutoFillType.java
+++ b/core/java/android/view/autofill/AutoFillType.java
@@ -21,16 +21,11 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
-import android.widget.TextView;
/**
* Defines the type of a object that can be used to auto-fill a {@link View} so the
* {@link android.service.autofill.AutoFillService} can use the proper {@link AutoFillValue} to
* fill it.
- *
- * <p>Some {@link AutoFillType}s can have an optional {@code sub-type}: the
- * main {@code type} defines the view's UI control category (like a text field), while the optional
- * {@code sub-type} define its semantics (like a postal address).
*/
public final class AutoFillType implements Parcelable {
@@ -38,37 +33,29 @@
// class idiom" (Effective Java, Item 71) to avoid memory utilization when auto-fill is not
// enabled.
private static class DefaultTypesHolder {
- static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE, 0);
- static final AutoFillType LIST = new AutoFillType(TYPE_LIST, 0);
+ static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT);
+ static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE);
+ static final AutoFillType LIST = new AutoFillType(TYPE_LIST);
+ static final AutoFillType DATE = new AutoFillType(TYPE_DATE);
}
private static final int TYPE_TEXT = 1;
private static final int TYPE_TOGGLE = 2;
private static final int TYPE_LIST = 3;
-
- // TODO(b/33197203): add others, like date picker? That would be trick, because they're set as:
- // updateDate(int year, int month, int dayOfMonth)
- // So, we would have to either use a long representing the Date.time(), or a custom long
- // representing:
- // year * 10000 + month * 100 + day
- // Then a custom getDatePickerValue(Bundle) that returns an immutable object with these 3 fields
+ private static final int TYPE_DATE = 4;
private final int mType;
- private final int mSubType;
- private AutoFillType(int type, int subType) {
+ private AutoFillType(int type) {
mType = type;
- mSubType = subType;
}
/**
* Checks if this is a type for a text field, which is filled by a {@link CharSequence}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
- * {@link AutoFillValue#forText(CharSequence)}, and the value of a bundle passed to auto-fill a
+ * {@link AutoFillValue#forText(CharSequence)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getTextValue()}.
- *
- * <p>Sub-type for this type is the value defined by {@link TextView#getInputType()}.
*/
public boolean isText() {
return mType == TYPE_TEXT;
@@ -78,10 +65,8 @@
* Checks if this is a a type for a togglable field, which is filled by a {@code boolean}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
- * {@link AutoFillValue#forToggle(boolean)}, and the value of a bundle passed to auto-fill a
+ * {@link AutoFillValue#forToggle(boolean)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getToggleValue()}.
- *
- * <p>This type has no sub-types.
*/
public boolean isToggle() {
return mType == TYPE_TOGGLE;
@@ -92,23 +77,26 @@
* representing the element index inside the list (starting at {@code 0}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
- * {@link AutoFillValue#forList(int)}, and the value of a bundle passed to auto-fill a
+ * {@link AutoFillValue#forList(int)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getListValue()}.
- *
- * <p>This type has no sub-types.
*/
public boolean isList() {
return mType == TYPE_LIST;
}
-
/**
- * Gets the optional sub-type, representing the {@link View}'s semantic.
+ * Checks if this is a type for a date and time, which is represented by a long representing
+ * the number of milliseconds since the standard base time known as "the epoch", namely
+ * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}.
*
- * @return {@code 0} if type does not support sub-types.
+ * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
+ * {@link AutoFillValue#forDate(long)}, and the values passed to
+ * auto-fill a {@link View} can be fetched through {@link AutoFillValue#getDateValue()}.
+ *
+ * <p>This type has no sub-types.
*/
- public int getSubType() {
- return mSubType;
+ public boolean isDate() {
+ return mType == TYPE_DATE;
}
/////////////////////////////////////
@@ -119,14 +107,13 @@
public String toString() {
if (!DEBUG) return super.toString();
- return "AutoFillType [type=" + mType + ", subType=" + mSubType + "]";
+ return "AutoFillType [type=" + mType + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + mSubType;
result = prime * result + mType;
return result;
}
@@ -137,7 +124,6 @@
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final AutoFillType other = (AutoFillType) obj;
- if (mSubType != other.mSubType) return false;
if (mType != other.mType) return false;
return true;
}
@@ -154,12 +140,10 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mType);
- parcel.writeInt(mSubType);
}
private AutoFillType(Parcel parcel) {
mType = parcel.readInt();
- mSubType = parcel.readInt();
}
public static final Parcelable.Creator<AutoFillType> CREATOR =
@@ -184,8 +168,8 @@
*
* <p>See {@link #isText()} for more info.
*/
- public static AutoFillType forText(int inputType) {
- return new AutoFillType(TYPE_TEXT, inputType);
+ public static AutoFillType forText() {
+ return DefaultTypesHolder.TEXT;
}
/**
@@ -206,4 +190,13 @@
public static AutoFillType forList() {
return DefaultTypesHolder.LIST;
}
+
+ /**
+ * Creates a type that represents a date.
+ *
+ * <p>See {@link #isDate()} for more info.
+ */
+ public static AutoFillType forDate() {
+ return DefaultTypesHolder.DATE;
+ }
}
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
index d9afa3b..c24e04e 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -35,11 +35,13 @@
private final String mText;
private final int mListIndex;
private final boolean mToggle;
+ private final long mDate;
- private AutoFillValue(CharSequence text, int listIndex, boolean toggle) {
+ private AutoFillValue(CharSequence text, int listIndex, boolean toggle, long date) {
mText = (text == null) ? null : text.toString();
mListIndex = listIndex;
mToggle = toggle;
+ mDate = date;
}
/**
@@ -69,6 +71,17 @@
return mListIndex;
}
+ /**
+ * Gets the value representing the the number of milliseconds since the standard base time known
+ * as "the epoch", namely January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}
+ * of a date field.
+ *
+ * <p>See {@link AutoFillType#isDate()} for more info.
+ */
+ public long getDateValue() {
+ return mDate;
+ }
+
/////////////////////////////////////
// Object "contract" methods. //
/////////////////////////////////////
@@ -77,9 +90,10 @@
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + mListIndex;
result = prime * result + ((mText == null) ? 0 : mText.hashCode());
+ result = prime * result + mListIndex;
result = prime * result + (mToggle ? 1231 : 1237);
+ result = prime * result + (int) (mDate ^ (mDate >>> 32));
return result;
}
@@ -89,13 +103,14 @@
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final AutoFillValue other = (AutoFillValue) obj;
- if (mListIndex != other.mListIndex) return false;
if (mText == null) {
if (other.mText != null) return false;
} else {
if (!mText.equals(other.mText)) return false;
}
+ if (mListIndex != other.mListIndex) return false;
if (mToggle != other.mToggle) return false;
+ if (mDate != other.mDate) return false;
return true;
}
@@ -113,7 +128,7 @@
return mText.length() + "_chars";
}
- return "[listIndex=" + mListIndex + ", toggle=" + mToggle + "]";
+ return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]";
}
/////////////////////////////////////
@@ -130,12 +145,14 @@
parcel.writeString(mText);
parcel.writeInt(mListIndex);
parcel.writeInt(mToggle ? 1 : 0);
+ parcel.writeLong(mDate);
}
private AutoFillValue(Parcel parcel) {
mText = parcel.readString();
mListIndex = parcel.readInt();
mToggle = parcel.readInt() == 1;
+ mDate = parcel.readLong();
}
public static final Parcelable.Creator<AutoFillValue> CREATOR =
@@ -157,31 +174,42 @@
// TODO(b/33197203): add unit tests for each supported type (new / get should return same value)
/**
- * Creates a new {@link AutoFillValue} to auto-fill a text field.
+ * Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a text field.
*
* <p>See {@link AutoFillType#isText()} for more info.
*/
// TODO(b/33197203): use cache
@Nullable
public static AutoFillValue forText(@Nullable CharSequence value) {
- return value == null ? null : new AutoFillValue(value, 0, false);
+ return value == null ? null : new AutoFillValue(value, 0, false, 0);
}
/**
- * Creates a new {@link AutoFillValue} to auto-fill a toggable field.
+ * Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a toggable
+ * field.
*
* <p>See {@link AutoFillType#isToggle()} for more info.
*/
public static AutoFillValue forToggle(boolean value) {
- return new AutoFillValue(null, 0, value);
+ return new AutoFillValue(null, 0, value, 0);
}
/**
- * Creates a new {@link AutoFillValue} to auto-fill a selection list field.
+ * Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a selection
+ * list.
*
* <p>See {@link AutoFillType#isList()} for more info.
*/
public static AutoFillValue forList(int value) {
- return new AutoFillValue(null, value, false);
+ return new AutoFillValue(null, value, false, 0);
+ }
+
+ /**
+ * Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a date.
+ *
+ * <p>See {@link AutoFillType#isDate()} for more info.
+ */
+ public static AutoFillValue forDate(long date) {
+ return new AutoFillValue(null, 0, false, date);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 887c59a..141b52f 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -591,7 +591,7 @@
@Override
public AutoFillType getAutoFillType() {
- return AutoFillType.forToggle();
+ return isEnabled() ? AutoFillType.forToggle() : null;
}
@Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 517e20cd..0ffefd0 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -31,7 +31,11 @@
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
+import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -170,6 +174,13 @@
if (firstDayOfWeek != 0) {
setFirstDayOfWeek(firstDayOfWeek);
}
+
+ mDelegate.setAutoFillChangeListener((v, y, m, d) -> {
+ final AutoFillManager afm = context.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ afm.valueChanged(this);
+ }
+ });
}
private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
@@ -503,12 +514,15 @@
OnDateChangedListener onDateChangedListener);
void setOnDateChangedListener(OnDateChangedListener onDateChangedListener);
+ void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener);
void updateDate(int year, int month, int dayOfMonth);
+ void updateDate(long date);
int getYear();
int getMonth();
int getDayOfMonth();
+ long getDate();
void setFirstDayOfWeek(int firstDayOfWeek);
int getFirstDayOfWeek();
@@ -558,6 +572,7 @@
// Callbacks
protected OnDateChangedListener mOnDateChangedListener;
+ protected OnDateChangedListener mAutoFillChangeListener;
protected ValidationCallback mValidationCallback;
public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
@@ -580,10 +595,28 @@
}
@Override
+ public void setAutoFillChangeListener(OnDateChangedListener callback) {
+ mAutoFillChangeListener = callback;
+ }
+
+ @Override
public void setValidationCallback(ValidationCallback callback) {
mValidationCallback = callback;
}
+ @Override
+ public void updateDate(long date) {
+ Calendar cal = Calendar.getInstance(mCurrentLocale);
+ cal.setTimeInMillis(date);
+ updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
+ cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ @Override
+ public long getDate() {
+ return mCurrentDate.getTimeInMillis();
+ }
+
protected void onValidationChanged(boolean valid) {
if (mValidationCallback != null) {
mValidationCallback.onValidationChanged(valid);
@@ -723,4 +756,31 @@
public interface ValidationCallback {
void onValidationChanged(boolean valid);
}
+
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
+
+ @Override
+ public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
+ // This view is self-sufficient for auto-fill, so it needs to call
+ // onProvideAutoFillStructure() to fill itself, but it does not need to call
+ // dispatchProvideAutoFillStructure() to fill its children.
+ onProvideAutoFillStructure(structure, flags);
+ }
+
+ @Override
+ public void autoFill(AutoFillValue value) {
+ if (!isEnabled()) return;
+
+ mDelegate.updateDate(value.getDateValue());
+ }
+
+ @Override
+ public AutoFillType getAutoFillType() {
+ return isEnabled() ? AutoFillType.forDate() : null;
+ }
+
+ @Override
+ public AutoFillValue getAutoFillValue() {
+ return isEnabled() ? AutoFillValue.forDate(mDelegate.getDate()) : null;
+ }
}
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 907250a..ca1bf58 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -390,10 +390,16 @@
private void onDateChanged(boolean fromUser, boolean callbackToClient) {
final int year = mCurrentDate.get(Calendar.YEAR);
- if (callbackToClient && mOnDateChangedListener != null) {
+ if (callbackToClient
+ && (mOnDateChangedListener != null || mAutoFillChangeListener != null)) {
final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
- mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
+ if (mOnDateChangedListener != null) {
+ mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
+ }
+ if (mAutoFillChangeListener != null) {
+ mAutoFillChangeListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
+ }
}
mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java
index 702b2a5..fc2d1fa 100644
--- a/core/java/android/widget/DatePickerSpinnerDelegate.java
+++ b/core/java/android/widget/DatePickerSpinnerDelegate.java
@@ -576,6 +576,10 @@
mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
getDayOfMonth());
}
+ if (mAutoFillChangeListener != null) {
+ mAutoFillChangeListener.onDateChanged(mDelegator, getYear(), getMonth(),
+ getDayOfMonth());
+ }
}
/**
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index bba3a11..be5fc53 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -436,7 +436,7 @@
@Override
public AutoFillType getAutoFillType() {
- return AutoFillType.forList();
+ return isEnabled() ? AutoFillType.forList() : null;
}
@Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 4937740..97fc835 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10002,6 +10002,7 @@
AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
}
structure.setHint(getHint());
+ structure.setInputType(getInputType());
}
// TODO(b/33197203): add unit/CTS tests for auto-fill methods
@@ -10018,7 +10019,7 @@
@Override
@Nullable
public AutoFillType getAutoFillType() {
- return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
+ return isTextEditable() ? AutoFillType.forText() : null;
}
@Override
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 9f38b04..1df202e 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -23,12 +23,17 @@
import android.annotation.Widget;
import android.content.Context;
import android.content.res.TypedArray;
+import android.icu.util.Calendar;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
+import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -132,6 +137,12 @@
this, context, attrs, defStyleAttr, defStyleRes);
break;
}
+ mDelegate.setAutoFillChangeListener((v, h, m) -> {
+ final AutoFillManager afm = context.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ afm.valueChanged(this);
+ }
+ });
}
/**
@@ -348,12 +359,16 @@
void setMinute(@IntRange(from = 0, to = 59) int minute);
int getMinute();
+ void setDate(long date);
+ long getDate();
+
void setIs24Hour(boolean is24Hour);
boolean is24Hour();
boolean validateInput();
void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
+ void setAutoFillChangeListener(OnTimeChangedListener autoFillChangeListener);
void setEnabled(boolean enabled);
boolean isEnabled();
@@ -398,6 +413,7 @@
protected final Locale mLocale;
protected OnTimeChangedListener mOnTimeChangedListener;
+ protected OnTimeChangedListener mAutoFillChangeListener;
public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
mDelegator = delegator;
@@ -410,6 +426,27 @@
mOnTimeChangedListener = callback;
}
+ @Override
+ public void setAutoFillChangeListener(OnTimeChangedListener callback) {
+ mAutoFillChangeListener = callback;
+ }
+
+ @Override
+ public void setDate(long date) {
+ Calendar cal = Calendar.getInstance(mLocale);
+ cal.setTimeInMillis(date);
+ setHour(cal.get(Calendar.HOUR_OF_DAY));
+ setMinute(cal.get(Calendar.MINUTE));
+ }
+
+ @Override
+ public long getDate() {
+ Calendar cal = Calendar.getInstance(mLocale);
+ cal.set(Calendar.HOUR_OF_DAY, getHour());
+ cal.set(Calendar.MINUTE, getMinute());
+ return cal.getTimeInMillis();
+ }
+
protected static class SavedState extends View.BaseSavedState {
private final int mHour;
private final int mMinute;
@@ -474,4 +511,31 @@
};
}
}
+
+ // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
+
+ @Override
+ public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
+ // This view is self-sufficient for auto-fill, so it needs to call
+ // onProvideAutoFillStructure() to fill itself, but it does not need to call
+ // dispatchProvideAutoFillStructure() to fill its children.
+ onProvideAutoFillStructure(structure, flags);
+ }
+
+ @Override
+ public void autoFill(AutoFillValue value) {
+ if (!isEnabled()) return;
+
+ mDelegate.setDate(value.getDateValue());
+ }
+
+ @Override
+ public AutoFillType getAutoFillType() {
+ return isEnabled() ? AutoFillType.forDate() : null;
+ }
+
+ @Override
+ public AutoFillValue getAutoFillValue() {
+ return isEnabled() ? AutoFillValue.forDate(mDelegate.getDate()) : null;
+ }
}
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 94bd342..3605585 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -671,6 +671,9 @@
if (mOnTimeChangedListener != null) {
mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
+ if (mAutoFillChangeListener != null) {
+ mAutoFillChangeListener.onTimeChanged(mDelegator, getHour(), getMinute());
+ }
}
private void tryVibrate() {
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 20a5512..813c30e3 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -495,6 +495,9 @@
mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
getMinute());
}
+ if (mAutoFillChangeListener != null) {
+ mAutoFillChangeListener.onTimeChanged(mDelegator, getHour(), getMinute());
+ }
}
private void updateHourControl() {
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index c840f26..ec3aac2 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -48,12 +48,12 @@
List<NotificationChannel> channelsList = new ArrayList<NotificationChannel>();
channelsList.add(new NotificationChannel(
VIRTUAL_KEYBOARD,
- context.getString(R.string.notification_channel_virtual_keyboard),
+ R.string.notification_channel_virtual_keyboard,
NotificationManager.IMPORTANCE_LOW));
final NotificationChannel physicalKeyboardChannel = new NotificationChannel(
PHYSICAL_KEYBOARD,
- context.getString(R.string.notification_channel_physical_keyboard),
+ R.string.notification_channel_physical_keyboard,
NotificationManager.IMPORTANCE_DEFAULT);
physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
@@ -61,32 +61,32 @@
channelsList.add(new NotificationChannel(
SECURITY,
- context.getString(R.string.notification_channel_security),
+ R.string.notification_channel_security,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
CAR_MODE,
- context.getString(R.string.notification_channel_car_mode),
+ R.string.notification_channel_car_mode,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
DEVELOPER,
- context.getString(R.string.notification_channel_developer),
+ R.string.notification_channel_developer,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
UPDATES,
- context.getString(R.string.notification_channel_updates),
+ R.string.notification_channel_updates,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
NETWORK_STATUS,
- context.getString(R.string.notification_channel_network_status),
+ R.string.notification_channel_network_status,
NotificationManager.IMPORTANCE_LOW));
final NotificationChannel networkAlertsChannel = new NotificationChannel(
NETWORK_ALERTS,
- context.getString(R.string.notification_channel_network_alerts),
+ R.string.notification_channel_network_alerts,
NotificationManager.IMPORTANCE_HIGH);
networkAlertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
@@ -94,17 +94,17 @@
channelsList.add(new NotificationChannel(
VPN,
- context.getString(R.string.notification_channel_vpn),
+ R.string.notification_channel_vpn,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
DEVICE_ADMIN,
- context.getString(R.string.notification_channel_device_admin),
+ R.string.notification_channel_device_admin,
NotificationManager.IMPORTANCE_LOW));
final NotificationChannel alertsChannel = new NotificationChannel(
ALERTS,
- context.getString(R.string.notification_channel_alerts),
+ R.string.notification_channel_alerts,
NotificationManager.IMPORTANCE_DEFAULT);
alertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
@@ -112,12 +112,12 @@
channelsList.add(new NotificationChannel(
RETAIL_MODE,
- context.getString(R.string.notification_channel_retail_mode),
+ R.string.notification_channel_retail_mode,
NotificationManager.IMPORTANCE_LOW));
channelsList.add(new NotificationChannel(
USB,
- context.getString(R.string.notification_channel_usb),
+ R.string.notification_channel_usb,
NotificationManager.IMPORTANCE_MIN));
nm.createNotificationChannels(channelsList);
@@ -128,7 +128,7 @@
final NotificationManager nm = context.getSystemService(NotificationManager.class);
nm.createNotificationChannelsForPackage(pkg, Arrays.asList(new NotificationChannel(
ACCOUNT,
- context.getString(R.string.notification_channel_account),
+ R.string.notification_channel_account,
NotificationManager.IMPORTANCE_LOW)));
}
diff --git a/core/java/com/android/internal/view/SurfaceCallbackHelper.java b/core/java/com/android/internal/view/SurfaceCallbackHelper.java
index 507b673..5b6a82c 100644
--- a/core/java/com/android/internal/view/SurfaceCallbackHelper.java
+++ b/core/java/com/android/internal/view/SurfaceCallbackHelper.java
@@ -17,11 +17,14 @@
package com.android.internal.view;
import android.os.RemoteException;
+import android.view.IWindow;
+import android.view.IWindowSession;
import android.view.Surface;
import android.view.SurfaceHolder;
public class SurfaceCallbackHelper {
- Runnable mRunnable;
+ IWindowSession mSession;
+ IWindow.Stub mWindow;
int mFinishDrawingCollected = 0;
int mFinishDrawingExpected = 0;
@@ -34,18 +37,26 @@
if (mFinishDrawingCollected < mFinishDrawingExpected) {
return;
}
- mRunnable.run();
+ try {
+ mSession.finishDrawing(mWindow);
+ } catch (RemoteException e) {
+ }
}
}
};
- public SurfaceCallbackHelper(Runnable callbacksCollected) {
- mRunnable = callbacksCollected;
+ public SurfaceCallbackHelper(IWindowSession session,
+ IWindow.Stub window) {
+ mSession = session;
+ mWindow = window;
}
public void dispatchSurfaceRedrawNeededAsync(SurfaceHolder holder, SurfaceHolder.Callback callbacks[]) {
if (callbacks == null || callbacks.length == 0) {
- mRunnable.run();
+ try {
+ mSession.finishDrawing(mWindow);
+ } catch (RemoteException e) {
+ }
return;
}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index c348cc5..a196540 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -232,6 +232,73 @@
return localBitmap->bitmap();
}
+void imageInfo(JNIEnv* env, jobject bitmap, AndroidBitmapInfo* info) {
+ SkASSERT(info);
+ SkASSERT(env);
+ SkASSERT(bitmap);
+ SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
+ jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ LocalScopedBitmap localBitmap(bitmapHandle);
+
+ const SkImageInfo& imageInfo = localBitmap->info();
+ info->width = imageInfo.width();
+ info->height = imageInfo.height();
+ info->stride = localBitmap->rowBytes();
+ info->flags = 0;
+ switch (imageInfo.colorType()) {
+ case kN32_SkColorType:
+ info->format = ANDROID_BITMAP_FORMAT_RGBA_8888;
+ break;
+ case kRGB_565_SkColorType:
+ info->format = ANDROID_BITMAP_FORMAT_RGB_565;
+ break;
+ case kARGB_4444_SkColorType:
+ info->format = ANDROID_BITMAP_FORMAT_RGBA_4444;
+ break;
+ case kAlpha_8_SkColorType:
+ info->format = ANDROID_BITMAP_FORMAT_A_8;
+ break;
+ default:
+ info->format = ANDROID_BITMAP_FORMAT_NONE;
+ break;
+ }
+}
+
+void* lockPixels(JNIEnv* env, jobject bitmap) {
+ SkASSERT(env);
+ SkASSERT(bitmap);
+ SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
+ jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+
+ LocalScopedBitmap localBitmap(bitmapHandle);
+ if (!localBitmap->valid()) return nullptr;
+
+ SkPixelRef& pixelRef = localBitmap->bitmap();
+ pixelRef.lockPixels();
+ if (!pixelRef.pixels()) {
+ pixelRef.unlockPixels();
+ return nullptr;
+ }
+ pixelRef.ref();
+ return pixelRef.pixels();
+}
+
+bool unlockPixels(JNIEnv* env, jobject bitmap) {
+ SkASSERT(env);
+ SkASSERT(bitmap);
+ SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
+ jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+
+ LocalScopedBitmap localBitmap(bitmapHandle);
+ if (!localBitmap->valid()) return false;
+
+ SkPixelRef& pixelRef = localBitmap->bitmap();
+ pixelRef.notifyPixelsChanged();
+ pixelRef.unlockPixels();
+ pixelRef.unref();
+ return true;
+}
+
} // namespace bitmap
} // namespace android
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index 387a128..a4bfc94 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -17,6 +17,7 @@
#define BITMAP_H_
#include <jni.h>
+#include <android/bitmap.h>
#include <SkBitmap.h>
#include <SkColorTable.h>
#include <SkImageInfo.h>
@@ -44,6 +45,13 @@
Bitmap& toBitmap(JNIEnv* env, jobject bitmap);
Bitmap& toBitmap(JNIEnv* env, jlong bitmapHandle);
+// NDK access
+void imageInfo(JNIEnv* env, jobject bitmap, AndroidBitmapInfo* info);
+// Returns a pointer to the pixels or nullptr if the bitmap is not valid
+void* lockPixels(JNIEnv* env, jobject bitmap);
+// Returns true if unlocked, false if the bitmap is no longer valid (destroyed)
+bool unlockPixels(JNIEnv* env, jobject bitmap);
+
/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
sync with isPremultiplied
*/
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 989a557..2439b82 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -346,7 +346,7 @@
<< serviceName;
::android::vintf::Transport transport =
- ::android::hardware::getTransport(ifaceName);
+ ::android::hardware::getTransport(ifaceName, serviceName);
if ( transport != ::android::vintf::Transport::EMPTY
&& transport != ::android::vintf::Transport::HWBINDER) {
LOG(ERROR) << "service " << ifaceName << " declares transport method "
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index edcbb3f..f221392 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -627,9 +627,9 @@
int register_android_view_RenderNode(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
- "updateSurfacePosition_renderWorker", "(JIIII)V");
+ "updateWindowPosition_renderWorker", "(JIIII)V");
gSurfaceViewPositionLostMethod = GetMethodIDOrDie(env, clazz,
- "surfacePositionLost_uiRtSync", "(J)V");
+ "windowPositionLost_uiRtSync", "(J)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index be86f5c..a81901d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -693,6 +693,7 @@
return JNI_TRUE;
}
+
static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject handleObject, jlong frameNumber) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -701,27 +702,6 @@
ctrl->deferTransactionUntil(handle, frameNumber);
}
-static void nativeDeferTransactionUntilSurface(JNIEnv* env, jclass clazz, jlong nativeObject,
- jobject surfaceObject, jlong frameNumber) {
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<Surface> barrier = reinterpret_cast<Surface *>(surfaceObject);
-
- ctrl->deferTransactionUntil(barrier, frameNumber);
-}
-
-static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong nativeObject,
- jobject newParentObject) {
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<IBinder> handle = ibinderForJavaObject(env, newParentObject);
-
- ctrl->reparentChildren(handle);
-}
-
-static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong nativeObject) {
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- ctrl->detachChildren();
-}
-
static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong nativeObject,
jint scalingMode) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -844,12 +824,6 @@
(void*)nativeSetDisplayPowerMode },
{"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
(void*)nativeDeferTransactionUntil },
- {"nativeDeferTransactionUntilSurface", "(JJJ)V",
- (void*)nativeDeferTransactionUntilSurface },
- {"nativeReparentChildren", "(JLandroid/os/IBinder;)V",
- (void*)nativeReparentChildren } ,
- {"nativeSeverChildren", "(J)V",
- (void*)nativeSeverChildren } ,
{"nativeSetOverrideScalingMode", "(JI)V",
(void*)nativeSetOverrideScalingMode },
{"nativeGetHandle", "(J)Landroid/os/IBinder;",
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 508d897..dad6958 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -24,7 +24,6 @@
#include <utils/RefBase.h>
#include <gui/SurfaceComposerClient.h>
-#include <gui/Surface.h>
namespace android {
@@ -46,13 +45,6 @@
return reinterpret_cast<jlong>(client);
}
-static jlong nativeCreateScoped(JNIEnv* env, jclass clazz, jlong surfaceObject) {
- Surface *parent = reinterpret_cast<Surface*>(surfaceObject);
- SurfaceComposerClient* client = new SurfaceComposerClient(parent->getIGraphicBufferProducer());
- client->incStrong((void*)nativeCreate);
- return reinterpret_cast<jlong>(client);
-}
-
static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
client->decStrong((void*)nativeCreate);
@@ -63,12 +55,11 @@
client->dispose();
}
+
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "nativeCreate", "()J",
(void*)nativeCreate },
- { "nativeCreateScoped", "(J)J",
- (void*)nativeCreateScoped },
{ "nativeDestroy", "(J)V",
(void*)nativeDestroy },
{ "nativeKill", "(J)V",
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 59a536b..9660de4 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -26,8 +26,8 @@
#include <sys/un.h>
#include <unistd.h>
+#include <android-base/logging.h>
#include <android-base/strings.h>
-#include <cutils/log.h>
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
@@ -137,7 +137,7 @@
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
- ALOGE("Unable to stat fd %d : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Unable to stat fd " << fd;
return NULL;
}
@@ -150,7 +150,8 @@
}
if (!whitelist->IsAllowed(socket_name)) {
- ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd);
+ LOG(ERROR) << "Socket name not whitelisted : " << socket_name
+ << " (fd=" << fd << ")";
return NULL;
}
@@ -168,7 +169,7 @@
// with the child process across forks but those should have been closed
// before we got to this point.
if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
- ALOGE("Unsupported st_mode %d", f_stat.st_mode);
+ LOG(ERROR) << "Unsupported st_mode " << f_stat.st_mode;
return NULL;
}
@@ -178,7 +179,7 @@
}
if (!whitelist->IsAllowed(file_path)) {
- ALOGE("Not whitelisted : %s", file_path.c_str());
+ LOG(ERROR) << "Not whitelisted : " << file_path;
return NULL;
}
@@ -187,7 +188,7 @@
// there won't be any races.
const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
if (fd_flags == -1) {
- ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFD)";
return NULL;
}
@@ -205,7 +206,7 @@
// their presence and pass them in to open().
int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
if (fs_flags == -1) {
- ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFL)";
return NULL;
}
@@ -224,6 +225,7 @@
bool FileDescriptorInfo::Restat() const {
struct stat f_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
+ PLOG(ERROR) << "Unable to restat fd " << fd;
return false;
}
@@ -241,31 +243,31 @@
const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
if (new_fd == -1) {
- ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno));
+ PLOG(ERROR) << "Failed open(" << file_path << ", " << open_flags << ")";
return false;
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
close(new_fd);
- ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFD, " << fd_flags << ")";
return false;
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
close(new_fd);
- ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFL, " << fs_flags << ")";
return false;
}
if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
close(new_fd);
- ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno));
+ PLOG(ERROR) << "Failed lseek64(" << new_fd << ", SEEK_SET)";
return false;
}
if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
close(new_fd);
- ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno));
+ PLOG(ERROR) << "Failed dup2(" << fd << ", " << new_fd << ")";
return false;
}
@@ -312,7 +314,10 @@
// ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
char buf[4096];
ssize_t len = readlink(path, buf, sizeof(buf));
- if (len == -1) return false;
+ if (len == -1) {
+ PLOG(ERROR) << "Readlink on " << fd << " failed.";
+ return false;
+ }
result->assign(buf, len);
return true;
@@ -325,12 +330,12 @@
socklen_t addr_len = sizeof(ss);
if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
- ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed getsockname(" << fd << ")";
return false;
}
if (addr->sa_family != AF_UNIX) {
- ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family);
+ LOG(ERROR) << "Unsupported socket (fd=" << fd << ") with family " << addr->sa_family;
return false;
}
@@ -339,13 +344,13 @@
size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
// This is an unnamed local socket, we do not accept it.
if (path_len == 0) {
- ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd);
+ LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with empty path.";
return false;
}
// This is a local socket with an abstract address, we do not accept it.
if (unix_addr->sun_path[0] == '\0') {
- ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd);
+ LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with abstract address.";
return false;
}
@@ -363,17 +368,17 @@
bool FileDescriptorInfo::DetachSocket() const {
const int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd < 0) {
- ALOGE("Failed to open /dev/null : %s", strerror(errno));
+ PLOG(ERROR) << "Failed to open /dev/null";
return false;
}
if (dup2(dev_null_fd, fd) == -1) {
- ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed dup2 on socket descriptor " << fd;
return false;
}
if (close(dev_null_fd) == -1) {
- ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno));
+ PLOG(ERROR) << "Failed close(" << dev_null_fd << ")";
return false;
}
@@ -384,7 +389,7 @@
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore) {
DIR* d = opendir(kFdPath);
if (d == NULL) {
- ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+ PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
return NULL;
}
int dir_fd = dirfd(d);
@@ -397,14 +402,14 @@
continue;
}
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
- ALOGI("Ignoring open file descriptor %d", fd);
+ LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd);
if (info == NULL) {
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
}
return NULL;
}
@@ -412,7 +417,7 @@
}
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
return NULL;
}
return new FileDescriptorTable(open_fd_map);
@@ -424,7 +429,7 @@
// First get the list of open descriptors.
DIR* d = opendir(kFdPath);
if (d == NULL) {
- ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+ PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
return false;
}
@@ -436,7 +441,7 @@
continue;
}
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
- ALOGI("Ignoring open file descriptor %d", fd);
+ LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
@@ -444,7 +449,7 @@
}
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
return false;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aea7a5d..5bfcda3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1285,7 +1285,7 @@
<permission android:name="android.permission.SCORE_NETWORKS"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows applications to request network
+ <!-- Allows applications to request network
recommendations and scores from the NetworkScoreService.
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.REQUEST_NETWORK_SCORES"
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index b910211..fd5154a 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -24,7 +24,7 @@
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:clipToPadding="false"
@@ -38,6 +38,7 @@
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="@dimen/notification_content_margin_end"
android:minHeight="@dimen/notification_min_content_height"
+ android:layout_marginBottom="@dimen/notification_content_margin_bottom"
android:clipToPadding="false"
android:orientation="vertical"
>
@@ -49,7 +50,6 @@
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="@dimen/notification_content_margin_bottom"
android:spacing="@dimen/notification_messaging_spacing" >
<com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
style="@style/Widget.Material.Notification.MessagingText"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c137ae2..55b8884 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2304,6 +2304,39 @@
<enum name="manual" value="2" />
</attr>
+ <!-- Describes the content of a view so that a auto-fill service can fill in the appropriate
+ data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. -->
+ <attr name="autoFillHint">
+ <!-- No hint. -->
+ <flag name="none" value="0" />
+ <!-- The view contains an email address. -->
+ <flag name="emailAddress" value="0x1" />
+ <!-- The view contains a real name. -->
+ <flag name="name" value="0x2" />
+ <!-- The view contains a user name. -->
+ <flag name="username" value="0x4" />
+ <!-- The view contains a password. -->
+ <flag name="password" value="0x8" />
+ <!-- The view contains a phone number. -->
+ <flag name="phone" value="0x10" />
+ <!-- The view contains a postal address. -->
+ <flag name="postalAddress" value="0x20" />
+ <!-- The view contains a postal code. -->
+ <flag name="postalCode" value="0x40" />
+ <!-- The view contains a credit card number. -->
+ <flag name="creditCardNumber" value="0x80" />
+ <!-- The view contains a credit card security code -->
+ <flag name="creditCardSecurityCode" value="0x100" />
+ <!-- The view contains a credit card expiration date -->
+ <flag name="creditCardExpirationDate" value="0x200" />
+ <!-- The view contains the month a credit card expires -->
+ <flag name="creditCardExpirationMonth" value="0x400" />
+ <!-- The view contains the year a credit card expires -->
+ <flag name="creditCardExpirationYear" value="0x800" />
+ <!-- The view contains the day a credit card expires -->
+ <flag name="creditCardExpirationDay" value="0x1000" />
+ </attr>
+
<!-- Boolean that controls whether a view can take focus while in touch mode.
If this is true for a view, that view can gain focus when clicked on, and can keep
focus if another view is clicked on that doesn't have this attribute set to true. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 76f4a76..deacc24b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1534,6 +1534,14 @@
of Android higher than the number given here, the permission will not
be requested. -->
<attr name="maxSdkVersion" format="integer" />
+ <!-- Optional: the system must support this feature for the permission to be
+ requested. If it doesn't support the feature, it will be as if the manifest didn't
+ request it at all. -->
+ <attr name="requiredFeature" format="string" />
+ <!-- Optional: the system must NOT support this feature for the permission to be
+ requested. If it does support the feature, it will be as if the manifest didn't
+ request it at all. -->
+ <attr name="requiredNotFeature" format="string" />
</declare-styleable>
<!-- The <code>uses-configuration</code> tag specifies
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 01737e7..df3962c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2797,6 +2797,11 @@
<public name="fontProviderAuthority" />
<public name="fontProviderQuery" />
<public name="autoFillMode" />
+ <public name="primaryContentAlpha" />
+ <public name="secondaryContentAlpha" />
+ <public name="requiredFeature" />
+ <public name="requiredNotFeature" />
+ <public name="autoFillHint" />
</public-group>
<public-group type="style" first-id="0x010302e0">
@@ -2806,9 +2811,6 @@
<public name="textAssist" />
</public-group>
- <public type="attr" name="primaryContentAlpha" />
- <public type="attr" name="secondaryContentAlpha" />
-
<!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index cf132890..b50955b 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -108,13 +108,13 @@
}
@SmallTest
- public void testWithMaskableBitmap() throws Exception {
+ public void testWithAdaptiveBitmap() throws Exception {
final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
final Canvas can1 = new Canvas(bm1);
can1.drawColor(0xFFFF0000);
- final Icon im1 = Icon.createWithMaskableBitmap(bm1);
+ final Icon im1 = Icon.createWithAdaptiveBitmap(bm1);
final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);
@@ -132,12 +132,12 @@
L("writing temp bitmaps to %s...", dir);
bm1.compress(Bitmap.CompressFormat.PNG, 100,
- new FileOutputStream(new File(dir, "maskable-bitmap1-original.png")));
+ new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png")));
test1.compress(Bitmap.CompressFormat.PNG, 100,
- new FileOutputStream(new File(dir, "maskable-bitmap1-test.png")));
+ new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png")));
if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) {
findBitmapDifferences(bm1, test1);
- fail("maskable bitmap1 differs, check " + dir);
+ fail("adaptive bitmap1 differs, check " + dir);
}
}
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index ff1312a..8ce7ed0 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -68,7 +68,7 @@
/** @hide */
public static final int TYPE_URI = 4;
/** @hide */
- public static final int TYPE_BITMAP_MASKABLE = 5;
+ public static final int TYPE_ADAPTIVE_BITMAP = 5;
private static final int VERSION_STREAM_SERIALIZER = 1;
@@ -103,7 +103,7 @@
* {@link #TYPE_RESOURCE},
* {@link #TYPE_DATA}, or
* {@link #TYPE_URI}.
- * {@link #TYPE_BITMAP_MASKABLE}
+ * {@link #TYPE_ADAPTIVE_BITMAP}
* @hide
*/
public int getType() {
@@ -115,7 +115,7 @@
* @hide
*/
public Bitmap getBitmap() {
- if (mType != TYPE_BITMAP && mType != TYPE_BITMAP_MASKABLE) {
+ if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
throw new IllegalStateException("called getBitmap() on " + this);
}
return (Bitmap) mObj1;
@@ -221,7 +221,7 @@
private static final String typeToString(int x) {
switch (x) {
case TYPE_BITMAP: return "BITMAP";
- case TYPE_BITMAP_MASKABLE: return "BITMAP_MASKABLE";
+ case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE";
case TYPE_DATA: return "DATA";
case TYPE_RESOURCE: return "RESOURCE";
case TYPE_URI: return "URI";
@@ -289,7 +289,7 @@
switch (mType) {
case TYPE_BITMAP:
return new BitmapDrawable(context.getResources(), getBitmap());
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
return new AdaptiveIconDrawable(null,
new BitmapDrawable(context.getResources(), getBitmap()));
case TYPE_RESOURCE:
@@ -395,7 +395,7 @@
* @hide
*/
public void convertToAshmem() {
- if ((mType == TYPE_BITMAP || mType == TYPE_BITMAP_MASKABLE) &&
+ if ((mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP) &&
getBitmap().isMutable() &&
getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
setBitmap(getBitmap().createAshmemBitmap());
@@ -416,7 +416,7 @@
switch (mType) {
case TYPE_BITMAP:
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
break;
case TYPE_DATA:
@@ -452,8 +452,8 @@
switch (type) {
case TYPE_BITMAP:
return createWithBitmap(BitmapFactory.decodeStream(inputStream));
- case TYPE_BITMAP_MASKABLE:
- return createWithMaskableBitmap(BitmapFactory.decodeStream(inputStream));
+ case TYPE_ADAPTIVE_BITMAP:
+ return createWithAdaptiveBitmap(BitmapFactory.decodeStream(inputStream));
case TYPE_DATA:
final int length = inputStream.readInt();
final byte[] data = new byte[length];
@@ -488,7 +488,7 @@
}
switch (mType) {
case TYPE_BITMAP:
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
return getBitmap() == otherIcon.getBitmap();
case TYPE_DATA:
return getDataLength() == otherIcon.getDataLength()
@@ -566,11 +566,11 @@
* by {@link AdaptiveIconDrawable}.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
- public static Icon createWithMaskableBitmap(Bitmap bits) {
+ public static Icon createWithAdaptiveBitmap(Bitmap bits) {
if (bits == null) {
throw new IllegalArgumentException("Bitmap must not be null.");
}
- final Icon rep = new Icon(TYPE_BITMAP_MASKABLE);
+ final Icon rep = new Icon(TYPE_ADAPTIVE_BITMAP);
rep.setBitmap(bits);
return rep;
}
@@ -679,7 +679,7 @@
final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
switch (mType) {
case TYPE_BITMAP:
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
sb.append(" size=")
.append(getBitmap().getWidth())
.append("x")
@@ -718,7 +718,7 @@
* Parcelable interface
*/
public int describeContents() {
- return (mType == TYPE_BITMAP || mType == TYPE_BITMAP_MASKABLE || mType == TYPE_DATA)
+ return (mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP || mType == TYPE_DATA)
? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
}
@@ -728,7 +728,7 @@
this(in.readInt());
switch (mType) {
case TYPE_BITMAP:
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
mObj1 = bits;
break;
@@ -767,7 +767,7 @@
dest.writeInt(mType);
switch (mType) {
case TYPE_BITMAP:
- case TYPE_BITMAP_MASKABLE:
+ case TYPE_ADAPTIVE_BITMAP:
final Bitmap bits = getBitmap();
getBitmap().writeToParcel(dest, flags);
break;
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b685431..c4bb72c 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -29,8 +29,8 @@
byte[] getCertificate(String alias);
byte[] getCaCertificates(String alias);
- // APIs used by CertInstaller
- void installCaCertificate(in byte[] caCertificate);
+ // APIs used by CertInstaller and DevicePolicyManager
+ String installCaCertificate(in byte[] caCertificate);
// APIs used by DevicePolicyManager
boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index bf7a779a..d6b1cf1 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -45,7 +45,6 @@
PURPOSE_DECRYPT,
PURPOSE_SIGN,
PURPOSE_VERIFY,
- PURPOSE_WRAP_KEY,
})
public @interface PurposeEnum {}
@@ -70,12 +69,6 @@
public static final int PURPOSE_VERIFY = 1 << 3;
/**
- * Purpose of key: wrapping key for secure key import.
- */
- public static final int PURPOSE_WRAP_KEY = 1 << 4;
-
-
- /**
* @hide
*/
public static abstract class Purpose {
@@ -91,8 +84,6 @@
return KeymasterDefs.KM_PURPOSE_SIGN;
case PURPOSE_VERIFY:
return KeymasterDefs.KM_PURPOSE_VERIFY;
- case PURPOSE_WRAP_KEY:
- return KeymasterDefs.KM_PURPOSE_WRAP_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
@@ -108,8 +99,6 @@
return PURPOSE_SIGN;
case KeymasterDefs.KM_PURPOSE_VERIFY:
return PURPOSE_VERIFY;
- case KeymasterDefs.KM_PURPOSE_WRAP_KEY:
- return PURPOSE_WRAP_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 35ff635..1b57e29 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -78,7 +78,7 @@
// Render all layers to be updated, in order. Defer in reverse order, so that they'll be
// updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse)
for (int i = layers.entries().size() - 1; i >= 0; i--) {
- RenderNode* layerNode = layers.entries()[i].renderNode;
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
// only schedule repaint if node still on layer - possible it may have been
// removed during a dropped frame, but layers may still remain scheduled so
// as not to lose info on what portion is damaged
diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h
index 5b1a854..38f3596 100644
--- a/libs/hwui/LayerUpdateQueue.h
+++ b/libs/hwui/LayerUpdateQueue.h
@@ -19,6 +19,7 @@
#include "Rect.h"
#include "utils/Macros.h"
+#include <utils/StrongPointer.h>
#include <vector>
#include <unordered_map>
@@ -35,7 +36,7 @@
Entry(RenderNode* renderNode, const Rect& damage)
: renderNode(renderNode)
, damage(damage) {}
- RenderNode* renderNode;
+ sp<RenderNode> renderNode;
Rect damage;
};
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 2e33609..374c1b1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -268,7 +268,7 @@
// Geometry
void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
- if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(floatCount < 2 || paint.nothingToDraw())) return;
floatCount &= ~0x1; // round down to nearest two
addOp(alloc().create_trivial<PointsOp>(
@@ -279,7 +279,7 @@
}
void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
- if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(floatCount < 4 || paint.nothingToDraw())) return;
floatCount &= ~0x3; // round down to nearest four
addOp(alloc().create_trivial<LinesOp>(
@@ -290,7 +290,7 @@
}
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<RectOp>(
Rect(left, top, right, bottom),
@@ -333,7 +333,7 @@
}
void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (paint.getStyle() == SkPaint::kFill_Style
&& (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
@@ -362,7 +362,7 @@
void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
addOp(alloc().create_trivial<RoundRectOp>(
@@ -398,7 +398,7 @@
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
// TODO: move to Canvas.h
- if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
drawOval(x - radius, y - radius, x + radius, y + radius, paint);
}
@@ -419,7 +419,7 @@
}
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<OvalOp>(
Rect(left, top, right, bottom),
@@ -430,7 +430,7 @@
void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (fabs(sweepAngle) >= 360.0f) {
drawOval(left, top, right, bottom, paint);
@@ -445,7 +445,7 @@
}
void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<PathOp>(
Rect(path.getBounds()),
@@ -543,7 +543,7 @@
void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) {
- if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ if (!glyphs || !positions || glyphCount <= 0 || paint.nothingToDraw()) return;
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
positions = refBuffer<float>(positions, glyphCount * 2);
@@ -563,7 +563,7 @@
glyphs[0] = layout.getGlyphId(i);
float x = hOffset + layout.getX(i);
float y = vOffset + layout.getY(i);
- if (PaintUtils::paintWillNotDrawText(paint)) return;
+ if (paint.nothingToDraw()) return;
const uint16_t* tempGlyphs = refBuffer<glyph_t>(glyphs, 1);
addOp(alloc().create_trivial<TextOnPathOp>(
*(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index c57b1b3..51a7d00 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -443,7 +443,7 @@
void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode) {
- if (CC_UNLIKELY(count < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(count < 2 || paint.nothingToDraw())) return;
// convert the floats into SkPoints
count >>= 1; // now it is the number of points
std::unique_ptr<SkPoint[]> pts(new SkPoint[count]);
@@ -469,49 +469,49 @@
}
void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
- if (CC_UNLIKELY(count < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
}
void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
mCanvas->drawRectCoords(left, top, right, bottom, paint);
}
void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
mCanvas->drawRegion(region, paint);
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawRoundRect(rect, rx, ry, paint);
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
- if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
mCanvas->drawCircle(x, y, radius, paint);
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawOval(oval, paint);
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
}
void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect;
SkRRect roundRect;
if (path.isOval(&rect)) {
@@ -698,7 +698,7 @@
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
- if (!text || !positions || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ if (!text || !positions || count <= 0 || paint.nothingToDraw()) return;
// Set align to left for drawing, as we don't want individual
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 117395b..39f11b8 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -197,12 +197,12 @@
if (needsLayer) {
canvas->saveLayer(bounds, &paint);
}
- canvas->drawDrawable(displayList->mDrawable.get());
+ displayList->draw(canvas);
if (needsLayer) {
canvas->restore();
}
} else {
- canvas->drawDrawable(displayList->mDrawable.get());
+ displayList->draw(canvas);
}
}
}
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index b4babcb..496f7ba 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -27,10 +27,6 @@
namespace uirenderer {
namespace skiapipeline {
-SkiaDisplayList::SkiaDisplayList(SkRect bounds) : mDrawable(SkLiteDL::New(bounds)) {
- SkASSERT(projectionReceiveIndex == -1);
-}
-
void SkiaDisplayList::syncContents() {
for (auto& functor : mChildFunctors) {
functor.syncFunctor();
@@ -41,7 +37,7 @@
}
bool SkiaDisplayList::reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) {
- reset(SkRect::MakeEmpty());
+ reset();
node->attachAvailableList(this);
return true;
}
@@ -102,10 +98,10 @@
return isDirty;
}
-void SkiaDisplayList::reset(SkRect bounds) {
+void SkiaDisplayList::reset() {
mProjectionReceiver = nullptr;
- mDrawable->reset(bounds);
+ mDisplayList.reset();
mMutableImages.clear();
mVectorDrawables.clear();
@@ -119,7 +115,7 @@
void SkiaDisplayList::output(std::ostream& output, uint32_t level) {
DumpOpsCanvas canvas(output, level, *this);
- mDrawable->draw(&canvas, nullptr);
+ mDisplayList.draw(&canvas);
}
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 439b999..6ee5922 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -22,7 +22,7 @@
#include <deque>
#include <SkLiteDL.h>
-#include <SkPictureRecorder.h>
+#include <SkLiteRecorder.h>
namespace android {
namespace uirenderer {
@@ -39,22 +39,22 @@
*/
class SkiaDisplayList : public DisplayList {
public:
- SkiaDisplayList(SkRect bounds);
+ SkiaDisplayList() { SkASSERT(projectionReceiveIndex == -1); }
virtual ~SkiaDisplayList() {
/* Given that we are using a LinearStdAllocator to store some of the
* SkDrawable contents we must ensure that any other object that is
* holding a reference to those drawables is destroyed prior to their
* deletion.
*/
- mDrawable.reset();
+ mDisplayList.reset();
}
/**
* This resets the DisplayList so that it behaves as if the object were newly
- * constructed with the provided bounds. The reuse avoids any overhead
- * associated with destroying the SkLiteDL as well as the deques and vectors.
+ * constructed. The reuse avoids any overhead associated with destroying
+ * the SkLiteDL as well as the deques and vectors.
*/
- void reset(SkRect bounds);
+ void reset();
/**
* Use the linear allocator to create any SkDrawables needed by the display
@@ -72,7 +72,7 @@
/**
* Returns true if the DisplayList does not have any recorded content
*/
- bool isEmpty() const override { return mDrawable->empty(); }
+ bool isEmpty() const override { return mDisplayList.empty(); }
/**
* Returns true if this list directly contains a GLFunctor drawing command.
@@ -126,18 +126,24 @@
*/
inline bool containsProjectionReceiver() const { return mProjectionReceiver; }
+ void attachRecorder(SkLiteRecorder* recorder, const SkIRect& bounds) {
+ recorder->reset(&mDisplayList, bounds);
+ }
+
+ void draw(SkCanvas* canvas) { mDisplayList.draw(canvas); }
+
void output(std::ostream& output, uint32_t level) override;
/**
* We use std::deque here because (1) we need to iterate through these
- * elements and (2) mDrawable holds pointers to the elements, so they cannot
- * relocate.
+ * elements and (2) mDisplayList holds pointers to the elements, so they
+ * cannot relocate.
*/
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<GLFunctorDrawable> mChildFunctors;
std::vector<SkImage*> mMutableImages;
std::vector<VectorDrawableRoot*> mVectorDrawables;
- sk_sp<SkLiteDL> mDrawable;
+ SkLiteDL mDisplayList;
//mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
//receiver. It is set at record time and used at both prepare and draw tree traversals to
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 430d6be..11dc1f4 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -80,7 +80,7 @@
void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
// Render all layers that need to be updated, in order.
for (size_t i = 0; i < layers.entries().size(); i++) {
- RenderNode* layerNode = layers.entries()[i].renderNode;
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
// only schedule repaint if node still on layer - possible it may have been
// removed during a dropped frame, but layers may still remain scheduled so
// as not to lose info on what portion is damaged
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index dbe0296..559d268 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -39,14 +39,11 @@
if (renderNode) {
mDisplayList = renderNode->detachAvailableList();
}
- SkRect bounds = SkRect::MakeWH(width, height);
- if (mDisplayList) {
- mDisplayList->reset(bounds);
- } else {
- mDisplayList.reset(new SkiaDisplayList(bounds));
+ if (!mDisplayList) {
+ mDisplayList.reset(new SkiaDisplayList());
}
- mRecorder.reset(mDisplayList->mDrawable.get());
+ mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
SkiaCanvas::reset(&mRecorder);
}
diff --git a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp
index 4db1cb9..91c7514 100644
--- a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp
@@ -48,11 +48,11 @@
EXPECT_EQ(3u, queue.entries().size());
- EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode.get());
EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
- EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
+ EXPECT_EQ(b.get(), queue.entries()[1].renderNode.get());
EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
- EXPECT_EQ(c.get(), queue.entries()[2].renderNode);
+ EXPECT_EQ(c.get(), queue.entries()[2].renderNode.get());
EXPECT_EQ(Rect(0, 0, 1, 1), queue.entries()[2].damage); // rounded out
}
@@ -65,7 +65,7 @@
EXPECT_EQ(1u, queue.entries().size());
- EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode.get());
EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
}
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f21b3f7..cbea501 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -44,9 +44,9 @@
canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
});
- auto skLiteDL = SkLiteDL::New(SkRect::MakeWH(1, 1));
+ SkLiteDL skLiteDL;
SkLiteRecorder canvas;
- canvas.reset(skLiteDL.get());
+ canvas.reset(&skLiteDL, SkIRect::MakeWH(1, 1));
canvas.translate(100, 100);
RenderNodeDrawable drawable(rootNode.get(), &canvas);
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 2925243..eda4a9d 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -331,7 +331,7 @@
// damage rect.
EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables());
EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
- EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode);
+ EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode.get());
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
canvasContext->destroy();
}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 0ac09ac..7fb75dc 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -18,6 +18,7 @@
#include <gtest/gtest.h>
#include <RecordingCanvas.h>
+#include <SkBlurDrawLooper.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
@@ -59,3 +60,21 @@
EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds);
EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix);
}
+
+TEST(SkiaCanvas, drawShadowLayer) {
+ auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+ SkiaCanvas canvas(surface->getCanvas());
+
+ // clear to white
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
+
+ SkPaint paint;
+ // it is transparent to ensure that we still draw the rect since it has a looper
+ paint.setColor(SK_ColorTRANSPARENT);
+ // this is how view's shadow layers are implemented
+ paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+ canvas.drawRect(3, 3, 7, 7, paint);
+
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+ ASSERT_NE(TestUtils::getColor(surface, 5, 5), SK_ColorWHITE);
+}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index be460bf..dd8f4b4 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -30,16 +30,13 @@
using namespace android::uirenderer::skiapipeline;
TEST(SkiaDisplayList, create) {
- SkRect bounds = SkRect::MakeWH(200, 200);
- SkiaDisplayList skiaDL(bounds);
+ SkiaDisplayList skiaDL;
ASSERT_TRUE(skiaDL.isEmpty());
ASSERT_FALSE(skiaDL.mProjectionReceiver);
- ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
}
TEST(SkiaDisplayList, reset) {
- SkRect bounds = SkRect::MakeWH(200, 200);
- SkiaDisplayList skiaDL(bounds);
+ SkiaDisplayList skiaDL;
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
@@ -47,10 +44,9 @@
skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
skiaDL.mMutableImages.push_back(nullptr);
skiaDL.mVectorDrawables.push_back(nullptr);
- skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
+ skiaDL.mDisplayList.drawAnnotation(SkRect::MakeWH(200, 200), "testAnnotation", nullptr);
skiaDL.mProjectionReceiver = &drawable;
- ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
ASSERT_FALSE(skiaDL.mChildNodes.empty());
ASSERT_FALSE(skiaDL.mChildFunctors.empty());
ASSERT_FALSE(skiaDL.mMutableImages.empty());
@@ -58,10 +54,8 @@
ASSERT_FALSE(skiaDL.isEmpty());
ASSERT_TRUE(skiaDL.mProjectionReceiver);
- bounds = SkRect::MakeWH(100, 100);
- skiaDL.reset(bounds);
+ skiaDL.reset();
- ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
ASSERT_TRUE(skiaDL.mChildNodes.empty());
ASSERT_TRUE(skiaDL.mChildFunctors.empty());
ASSERT_TRUE(skiaDL.mMutableImages.empty());
@@ -79,7 +73,7 @@
ASSERT_EQ(availableList.get(), nullptr);
// attach a displayList for reuse
- SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
+ SkiaDisplayList skiaDL;
ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get(), nullptr));
// detach the list that you just attempted to reuse
@@ -93,13 +87,13 @@
}
TEST(SkiaDisplayList, syncContexts) {
- SkRect bounds = SkRect::MakeWH(200, 200);
- SkiaDisplayList skiaDL(bounds);
+ SkiaDisplayList skiaDL;
SkCanvas dummyCanvas;
TestUtils::MockFunctor functor;
skiaDL.mChildFunctors.emplace_back(&functor, nullptr, &dummyCanvas);
+ SkRect bounds = SkRect::MakeWH(200, 200);
VectorDrawableRoot vectorDrawable(new VectorDrawable::Group());
vectorDrawable.mutateStagingProperties()->setBounds(bounds);
skiaDL.mVectorDrawables.push_back(&vectorDrawable);
@@ -127,7 +121,7 @@
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
- SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
+ SkiaDisplayList skiaDL;
// prepare with a clean VD
VectorDrawableRoot cleanVD(new VectorDrawable::Group());
@@ -170,8 +164,7 @@
}
TEST(SkiaDisplayList, updateChildren) {
- SkRect bounds = SkRect::MakeWH(200, 200);
- SkiaDisplayList skiaDL(bounds);
+ SkiaDisplayList skiaDL;
sp<RenderNode> renderNode = new RenderNode();
SkCanvas dummyCanvas;
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 845a3ea..2673be1c 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -39,21 +39,6 @@
return GL_NEAREST;
}
- // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
- static inline bool paintWillNotDraw(const SkPaint& paint) {
- return paint.getAlpha() == 0
- && !paint.getColorFilter()
- && paint.getBlendMode() == SkBlendMode::kSrcOver;
- }
-
- // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
- static inline bool paintWillNotDrawText(const SkPaint& paint) {
- return paint.getAlpha() == 0
- && paint.getLooper() == nullptr
- && !paint.getColorFilter()
- && paint.getBlendMode() == SkBlendMode::kSrcOver;
- }
-
static bool isOpaquePaint(const SkPaint* paint) {
if (!paint) return true; // default (paintless) behavior is SrcOver, black
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 1ca9658..d264127 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3188,9 +3188,55 @@
/**
* Returns Analytics/Metrics data about the current content being
*
- * @return a Bundle containint the set of attributes and values available
+ * @return a Bundle containing the set of attributes and values available
* for the media being handled by this instance of MediaCodec
*
+ * <table style="width: 0%">
+ * <thead>
+ * <tr>
+ * <th>Key</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@code "codec"}</td>
+ * <td>String</td>
+ * <td>Identifies the particular codec in use</td>
+ * </tr><tr>
+ * <td>{@code "mime"}</td>
+ * <td>String</td>
+ * <td>Mime type of the media being encoded/decoded</td>
+ * </tr><tr>
+ * <td>{@code "mode"}</td>
+ * <td>String</td>
+ * <td>"Audio" or "Video"</td>
+ * </tr><tr>
+ * <td>{@code "secure"}</td>
+ * <td>Integer</td>
+ * <td>Indicates whether the code is operating on secure content and
+ * may also use capabilities in android.media.MediaCrypto</td>
+ * </tr><tr>
+ * <td>{@code "height"}</td>
+ * <td>Integer</td>
+ * <td>Height (pixels); valid only when mode=video</td>
+ * </tr><tr>
+ * <td>{@code "width"}</td>
+ * <td>Integer</td>
+ * <td>Width (pixels); valid only when mode=video</td>
+ * </tr><tr>
+ * <td>{@code "rotation"}</td>
+ * <td>Integer</td>
+ * <td>rotation (degrees) to orient the video onto the target surface;
+ * valid only when mode=video. Note there may be additional
+ * rotations applied when the surface is mapped to the screen.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * Additional fields specific to individual codecs will also appear in
+ * the return value.
*/
public native Bundle getMetrics();
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 01ae36ff..b9e409d 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -656,6 +656,33 @@
* @return the set of keys and values available for the media being
* handled by this instance of MediaExtractor
*
+ * <table style="width: 0%">
+ * <thead>
+ * <tr>
+ * <th>Key</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@code "fmt"}</td>
+ * <td>String</td>
+ * <td>The container format (which determines the handler)</td>
+ * </tr><tr>
+ * <td>{@code "mime"}</td>
+ * <td>String</td>
+ * <td>Mime type of the container.</td>
+ * </tr><tr>
+ * <td>{@code "ntrk"}</td>
+ * <td>Integer</td>
+ * <td>Number of tracks in the container</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * Additional fields specific to individual codecs will also appear in
+ * the return value.
*/
public native Bundle getMetrics();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index fb37f9f..b85c911 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1498,6 +1498,64 @@
*
* @return the a map of attributes and values available for this video
* player or null if no metrics are available.
+ *
+ * <table style="width: 0%">
+ * <thead>
+ * <tr>
+ * <th>Key</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@code "video/codec"}</td>
+ * <td>String</td>
+ * <td>Identifies the video codec in use</td>
+ * </tr><tr>
+ * <td>{@code "video/mime"}</td>
+ * <td>String</td>
+ * <td>Mime type of the video being encoded/decoded</td>
+ * </tr><tr>
+ * <td>{@code "audio/codec"}</td>
+ * <td>String</td>
+ * <td>Identifies the audio codec in use</td>
+ * </tr><tr>
+ * <td>{@code "audio/mime"}</td>
+ * <td>String</td>
+ * <td>Mime type of the audio being encoded/decoded</td>
+ * </tr><tr>
+ * <td>{@code "ht"}</td>
+ * <td>Integer</td>
+ * <td>Height (pixels); valid only when mode=video</td>
+ * </tr><tr>
+ * <td>{@code "wid"}</td>
+ * <td>Integer</td>
+ * <td>Width (pixels); valid only when mode=video</td>
+ * </tr><tr>
+ * <td>{@code "frame"}</td>
+ * <td>Integer</td>
+ * <td>Number of decoded video frames sent to the display</td>
+ * </tr><tr>
+ * <td>{@code "dropped"}</td>
+ * <td>Integer</td>
+ * <td>Number of decoded video frames that were not sent to display.
+ * These frames were dropped by the player.</td>
+ * </tr><tr>
+ * <td>{@code "durationMs"}</td>
+ * <td>Integer</td>
+ * <td>The length of the media being played (in ms), e.g. "This video lasts for 30000 milliseconds". </td>
+ * </tr><tr>
+ * <td>{@code "playingMs"}</td>
+ * <td>Integer</td>
+ * <td>The time the media has been played (in ms). If you watch a
+ * 30 second twice through, this will report 60000 ms.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * Additional fields specific to individual codecs will also appear in
+ * the return value.
*/
public native Bundle getMetrics();
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index f79cd06..4b43260 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -35,6 +35,10 @@
* Instances of this class are created by {@link MidiManager#openDevice}.
*/
public final class MidiDevice implements Closeable {
+ static {
+ System.loadLibrary("media_jni");
+ }
+
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
@@ -43,6 +47,7 @@
private final IBinder mClientToken;
private final IBinder mDeviceToken;
private boolean mIsDeviceClosed;
+ private boolean mIsMirroredToNative;
private final CloseGuard mGuard = CloseGuard.get();
@@ -209,10 +214,45 @@
}
}
+ /**
+ * Makes Midi Device available to the Native API
+ * @hide
+ */
+ public void mirrorToNative() throws IOException {
+ if (mIsDeviceClosed || mIsMirroredToNative) {
+ return;
+ }
+
+ int result = mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId());
+ if (result != 0) {
+ throw new IOException("Failed mirroring to native: " + result);
+ }
+
+ mIsMirroredToNative = true;
+ }
+
+ /**
+ * Makes Midi Device no longer available to the Native API
+ * @hide
+ */
+ public void removeFromNative() throws IOException {
+ if (!mIsMirroredToNative) {
+ return;
+ }
+
+ int result = removeFromNative(mDeviceInfo.getId());
+ if (result != 0) {
+ throw new IOException("Failed removing from native: " + result);
+ }
+
+ mIsMirroredToNative = false;
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
if (!mIsDeviceClosed) {
+ removeFromNative();
mGuard.close();
mIsDeviceClosed = true;
try {
@@ -238,4 +278,7 @@
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString());
}
+
+ private native int mirrorToNative(IBinder deviceServerBinder, int uid);
+ private native int removeFromNative(int uid);
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index e5af357..aee9d38e 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -776,8 +776,8 @@
mSurface = null;
mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
@Override
- protected void updateSurface() {
- super.updateSurface();
+ protected void updateWindow() {
+ super.updateWindow();
relayoutSessionOverlayView();
}};
// The surface view's content should be treated as secure all the time.
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 861ed0a..23bf3d6 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -26,6 +26,7 @@
android_mtp_MtpDatabase.cpp \
android_mtp_MtpDevice.cpp \
android_mtp_MtpServer.cpp \
+ midi/android_media_midi_MidiDevice.cpp \
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
@@ -34,6 +35,7 @@
libbinder \
libmedia \
libmediadrm \
+ libmidi \
libskia \
libui \
liblog \
@@ -55,6 +57,7 @@
external/tremor/Tremor \
frameworks/base/core/jni \
frameworks/base/libs/hwui \
+ frameworks/base/media/native \
frameworks/av/media/libmedia \
frameworks/av/media/libstagefright \
frameworks/av/media/mtp \
diff --git a/media/jni/midi/android_media_midi_MidiDevice.cpp b/media/jni/midi/android_media_midi_MidiDevice.cpp
new file mode 100644
index 0000000..1e54bac
--- /dev/null
+++ b/media/jni/midi/android_media_midi_MidiDevice.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Midi-JNI"
+
+#include <android_util_Binder.h>
+#include <midi/MidiDeviceRegistry.h>
+#include <nativehelper/jni.h>
+#include <utils/Log.h>
+
+using namespace android;
+using namespace android::media::midi;
+
+extern "C" jint Java_android_media_midi_MidiDevice_mirrorToNative(
+ JNIEnv *env, jobject thiz, jobject midiDeviceServer, jint id)
+{
+ (void)thiz;
+ sp<IBinder> serverBinder = ibinderForJavaObject(env, midiDeviceServer);
+ if (serverBinder.get() == NULL) {
+ ALOGE("Could not obtain IBinder from passed jobject");
+ return -EINVAL;
+ }
+ // return MidiDeviceManager::getInstance().addDevice(serverBinder, uid);
+ return MidiDeviceRegistry::getInstance().addDevice(
+ new BpMidiDeviceServer(serverBinder), id);
+}
+
+extern "C" jint Java_android_media_midi_MidiDevice_removeFromNative(
+ JNIEnv *env, jobject thiz, jint uid)
+{
+ (void)env;
+ (void)thiz;
+ // return MidiDeviceManager::getInstance().removeDevice(uid);
+ return MidiDeviceRegistry::getInstance().removeDevice(uid);
+}
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
new file mode 100644
index 0000000..3500805
--- /dev/null
+++ b/media/native/midi/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 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.
+
+// The headers module is in frameworks/media/native/midi/Android.bp.
+ndk_library {
+ name: "libmidi.ndk",
+ symbol_file: "libmidi.map.txt",
+ first_version: "26",
+// unversioned_until: "current",
+}
diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk
new file mode 100644
index 0000000..b91c430
--- /dev/null
+++ b/media/native/midi/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ ../../java/android/media/midi/IMidiDeviceServer.aidl \
+ midi.cpp \
+ MidiDeviceRegistry.cpp \
+ MidiPortRegistry.cpp
+
+LOCAL_AIDL_INCLUDES := \
+ $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
+ frameworks/native/aidl/binder
+
+LOCAL_CFLAGS += -Wall -Werror -O0
+
+LOCAL_MODULE := libmidi
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/native/midi/MidiDeviceRegistry.cpp b/media/native/midi/MidiDeviceRegistry.cpp
new file mode 100644
index 0000000..8854a08
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MidiDeviceRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiDeviceRegistry);
+
+namespace media {
+namespace midi {
+
+MidiDeviceRegistry::MidiDeviceRegistry() : mNextDeviceToken(1) {
+}
+
+status_t MidiDeviceRegistry::addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId) {
+ if (server.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ mServers[deviceId] = server;
+ return OK;
+}
+
+status_t MidiDeviceRegistry::removeDevice(int32_t deviceId) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ mServers.erase(deviceId);
+ const auto& iter = mUidToToken.find(deviceId);
+ if (iter != mUidToToken.end()) {
+ mTokenToUid.erase(iter->second);
+ mUidToToken.erase(iter);
+ }
+ return OK;
+}
+
+//NOTE: This creates an entry if not found, or returns an existing one.
+status_t MidiDeviceRegistry::obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ const auto& serversIter = mServers.find(deviceId);
+ if (serversIter == mServers.end()) {
+ // Not found.
+ return -EINVAL;
+ }
+
+ const auto& iter = mUidToToken.find(deviceId);
+ if (iter != mUidToToken.end()) {
+ *deviceTokenPtr = iter->second;
+ } else {
+ *deviceTokenPtr = mNextDeviceToken++;
+ mTokenToUid[*deviceTokenPtr] = deviceId;
+ mUidToToken[deviceId] = *deviceTokenPtr;
+ }
+ return OK;
+}
+
+status_t MidiDeviceRegistry::releaseDevice(AMIDI_Device deviceToken) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ const auto& iter = mTokenToUid.find(deviceToken);
+ if (iter == mTokenToUid.end()) {
+ // Not found
+ return -EINVAL;
+ }
+
+ mServers.erase(iter->second);
+ mUidToToken.erase(iter->second);
+ mTokenToUid.erase(iter);
+ return OK;
+}
+
+status_t MidiDeviceRegistry::getDeviceByToken(
+ AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ int32_t id = -1;
+ {
+ const auto& iter = mTokenToUid.find(deviceToken);
+ if (iter == mTokenToUid.end()) {
+ return -EINVAL;
+ }
+ id = iter->second;
+ }
+ const auto& iter = mServers.find(id);
+ if (iter == mServers.end()) {
+ return -EINVAL;
+ }
+
+ *devicePtr = iter->second;
+ return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiDeviceRegistry.h b/media/native/midi/MidiDeviceRegistry.h
new file mode 100644
index 0000000..93be733
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+
+#include <map>
+#include <mutex>
+
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains a thread-safe, (singleton) list of MIDI devices with associated Binder interfaces,
+ * which are exposed to the Native API via (Java) MidiDevice.mirrorToNative() &
+ * MidiDevice.removeFromNative().
+ * (Called via MidiDeviceManager::addDevice() MidiManager::removeDevice()).
+ */
+class MidiDeviceRegistry : public Singleton<MidiDeviceRegistry> {
+ public:
+ /* Add a MIDI Device to the registry.
+ *
+ * server The Binder interface to the MIDI device server.
+ * deviceUId The unique ID of the device obtained from
+ * the Java API via MidiDeviceInfo.getId().
+ */
+ status_t addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId);
+
+ /* Remove the device (and associated server) from the Device registry.
+ *
+ * deviceUid The ID of the device which was used in the call to addDevice().
+ */
+ status_t removeDevice(int32_t deviceId);
+
+ /* Gets a device token associated with the device ID. This is used by the
+ * native API to identify/access the device.
+ * Multiple calls without releasing the token will return the same value.
+ *
+ * deviceUid The ID of the device.
+ * deviceTokenPtr Receives the device (native) token associated with the device ID.
+ * returns: OK on success, error code otherwise.
+ */
+ status_t obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr);
+
+ /*
+ * Releases the native API device token associated with a MIDI device.
+ *
+ * deviceToken The device (native) token associated with the device ID.
+ */
+ status_t releaseDevice(AMIDI_Device deviceToken);
+
+ /*
+ * Gets the Device server binder interface associated with the device token.
+ *
+ * deviceToken The device (native) token associated with the device ID.
+ */
+ status_t getDeviceByToken(AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr);
+
+ private:
+ friend class Singleton<MidiDeviceRegistry>;
+ MidiDeviceRegistry();
+
+ // Access Mutex
+ std::mutex mMapsLock;
+
+ // maps device IDs to servers
+ std::map<int32_t, sp<BpMidiDeviceServer>> mServers;
+
+ // maps device tokens to device ID
+ std::map<AMIDI_Device, int32_t> mTokenToUid;
+
+ // maps device IDs to device tokens
+ std::map<int32_t, AMIDI_Device> mUidToToken;
+
+ // Value of next device token to dole out.
+ AMIDI_Device mNextDeviceToken;
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
diff --git a/media/native/midi/MidiPortRegistry.cpp b/media/native/midi/MidiPortRegistry.cpp
new file mode 100644
index 0000000..fa70af8
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MidiPortRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiPortRegistry);
+
+namespace media {
+namespace midi {
+
+//TODO Note that these 2 are identical
+struct MidiPortRegistry::OutputPort {
+ AMIDI_Device device;
+ sp<IBinder> binderToken;
+ base::unique_fd ufd;
+};
+
+struct MidiPortRegistry::InputPort {
+ AMIDI_Device device;
+ sp<IBinder> binderToken;
+ base::unique_fd ufd;
+};
+
+MidiPortRegistry::MidiPortRegistry() : mNextOutputPortToken(0), mNextInputPortToken(0) {
+}
+
+status_t MidiPortRegistry::addOutputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_OutputPort *portPtr) {
+ *portPtr = mNextOutputPortToken++;
+
+ OutputPortEntry* portEntry = new OutputPortEntry;
+ portEntry->port = new OutputPort;
+ portEntry->state = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ portEntry->port = new OutputPort;
+ portEntry->port->device = device;
+ portEntry->port->binderToken = portToken;
+ portEntry->port->ufd = std::move(ufd);
+
+ mOutputPortMap[*portPtr] = portEntry;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::removeOutputPort(
+ AMIDI_OutputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr) {
+ OutputPortMap::iterator itr = mOutputPortMap.find(port);
+ if (itr == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ while (!entry->state.compare_exchange_weak(portState, MIDI_OUTPUT_PORT_STATE_CLOSED)) {
+ if (portState == MIDI_OUTPUT_PORT_STATE_CLOSED) {
+ return -EINVAL; // Already closed
+ }
+ }
+ *devicePtr = entry->port->device;
+ *portTokenPtr = entry->port->binderToken;
+ delete entry->port;
+ entry->port = nullptr;
+
+ mOutputPortMap.erase(itr);
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getOutputPortFdAndLock(
+ AMIDI_OutputPort port, base::unique_fd **ufdPtr) {
+ if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ if (!entry->state.compare_exchange_strong(portState, MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE)) {
+ // The port has been closed.
+ return -EPIPE;
+ }
+ *ufdPtr = &entry->port->ufd;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::unlockOutputPort(AMIDI_OutputPort port) {
+ if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ entry->state.store(MIDI_OUTPUT_PORT_STATE_OPEN_IDLE);
+ return OK;
+}
+
+status_t MidiPortRegistry::addInputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_InputPort *portPtr) {
+ *portPtr = mNextInputPortToken++;
+
+ InputPortEntry *entry = new InputPortEntry;
+
+ entry->state = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ entry->port = new InputPort;
+ entry->port->device = device;
+ entry->port->binderToken = portToken;
+ entry->port->ufd = std::move(ufd);
+
+ mInputPortMap[*portPtr] = entry;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::removeInputPort(
+ AMIDI_InputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr) {
+ InputPortMap::iterator itr = mInputPortMap.find(port);
+ if (itr == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+ int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ while (!entry->state.compare_exchange_weak(portState, MIDI_INPUT_PORT_STATE_CLOSED)) {
+ if (portState == MIDI_INPUT_PORT_STATE_CLOSED) return -EINVAL; // Already closed
+ }
+
+ *devicePtr = entry->port->device;
+ *portTokenPtr = entry->port->binderToken;
+ delete entry->port;
+ entry->port = nullptr;
+
+ mInputPortMap.erase(itr);
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+
+ *ufdPtr = &entry->port->ufd;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+
+ int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ if (!entry->state.compare_exchange_strong(portState, MIDI_INPUT_PORT_STATE_OPEN_ACTIVE)) {
+ // The port has been closed.
+ return -EPIPE;
+ }
+ *ufdPtr = &entry->port->ufd;
+ return OK;
+}
+
+status_t MidiPortRegistry::MidiPortRegistry::unlockInputPort(AMIDI_InputPort port) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+ entry->state.store(MIDI_INPUT_PORT_STATE_OPEN_IDLE);
+ return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiPortRegistry.h b/media/native/midi/MidiPortRegistry.h
new file mode 100644
index 0000000..f1ffb78
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+
+#include <atomic>
+#include <map>
+
+#include <android-base/unique_fd.h>
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains lists of all active input and output MIDI ports and controls access to them. Provides
+ * exclusive access to specific MIDI ports.
+ */
+class MidiPortRegistry : public Singleton<MidiPortRegistry> {
+ public:
+ /*
+ * Creates an output port entry and associates it with the specified MIDI device.
+ * Called by AMIDI_openOutputPort();
+ *
+ * device The native API device ID.
+ * portToken The port token (returned from the device server).
+ * udf File descriptor for the data port associated with the MIDI output port.
+ * portPtr Receives the native API port ID of the port being opened.
+ */
+ status_t addOutputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_OutputPort *portPtr);
+
+ /*
+ * Removes for the output port list a previously added output port.
+ * Called by AMIDI_closeOutputPort();
+ *
+ * port The native API port ID of the port being closed.
+ * devicePtr Receives the native API device ID associated with the port.
+ * portTokenPtr Receives the binder token associated with the port.
+ */
+ status_t removeOutputPort(
+ AMIDI_OutputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr);
+
+ /*
+ * Creates an input port entry and associates it with the specified MIDI device.
+ * Called by AMIDI_openInputPort();
+ *
+ * device The native API device ID.
+ * portToken The port token (returned from the device server).
+ * udf File descriptor for the data port associated with the MIDI input port.
+ * portPtr Receives the native API port ID of the port being opened.
+ */
+ status_t addInputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_InputPort *portPtr);
+
+ /*
+ * Removes for the input port list a previously added input port.
+ * Called by AMIDI_closeINputPort();
+ *
+ * port The native API port ID of the port being closed.
+ * devicePtr Receives the native API device ID associated with the port.
+ * portTokenPtr Receives the binder token associated with the port.
+ */
+ status_t removeInputPort(
+ AMIDI_InputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr);
+
+ /*
+ * Retrieves an exclusive-access file descriptor for an output port.
+ * Called from AMIDI_receive().
+ *
+ * port The native API id of the output port.
+ * ufdPtr Receives the exclusive-access file descriptor for the output port.
+ */
+ status_t getOutputPortFdAndLock(AMIDI_OutputPort port, base::unique_fd **ufdPtr);
+
+ /*
+ * Releases exclusive-access to the port and invalidates the previously received file
+ * descriptor.
+ * Called from AMIDI_receive().
+ *
+ * port The native API id of the output port.
+ */
+ status_t unlockOutputPort(AMIDI_OutputPort port);
+
+ /*
+ * Retrieves an exclusive-access file descriptor for an input port.
+ * (Not being used as (perhaps) AMIDI_sendWithTimestamp() doesn't need exclusive access
+ * to the port).
+ *
+ * port The native API id of the input port.
+ * ufdPtr Receives the exclusive-access file descriptor for the input port.
+ */
+ status_t getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+ /*
+ * Releases exclusive-access to the port and invalidates the previously received file
+ * descriptor.
+ * (Not used. See above).
+ *
+ * port The native API id of the input port.
+ */
+ status_t unlockInputPort(AMIDI_InputPort port);
+
+ /*
+ * Retrieves an unlocked (multi-access) file descriptor for an input port.
+ * Used by AMIDI_sendWith(), AMIDI_sendWithTimestamp & AMIDI_flush.
+ *
+ * port The native API id of the input port.
+ * ufdPtr Receives the multi-access file descriptor for the input port.
+ */
+ status_t getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+ private:
+ friend class Singleton<MidiPortRegistry>;
+ MidiPortRegistry();
+
+ /*
+ * Output (data receiving) ports.
+ */
+ struct OutputPort;
+ enum {
+ MIDI_OUTPUT_PORT_STATE_CLOSED = 0,
+ MIDI_OUTPUT_PORT_STATE_OPEN_IDLE,
+ MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE
+ };
+
+ struct OutputPortEntry {
+ std::atomic_int state;
+ OutputPort *port;
+ };
+
+ typedef std::map<AMIDI_OutputPort, OutputPortEntry*> OutputPortMap;
+ // Access is synchronized per record via 'state' field.
+ std::atomic<AMIDI_OutputPort> mNextOutputPortToken;
+ OutputPortMap mOutputPortMap;
+
+ /*
+ * Input (data sending) ports.
+ */
+ struct InputPort;
+ enum {
+ MIDI_INPUT_PORT_STATE_CLOSED = 0,
+ MIDI_INPUT_PORT_STATE_OPEN_IDLE,
+ MIDI_INPUT_PORT_STATE_OPEN_ACTIVE
+ };
+
+ struct InputPortEntry {
+ std::atomic_int state;
+ InputPort *port;
+ };
+
+ typedef std::map<AMIDI_OutputPort, InputPortEntry*> InputPortMap;
+ // Access is synchronized per record via 'state' field.
+ std::atomic<AMIDI_InputPort> mNextInputPortToken;
+ InputPortMap mInputPortMap;
+
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp
new file mode 100644
index 0000000..1bf0bd0
--- /dev/null
+++ b/media/native/midi/midi.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "NativeMIDI"
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceRegistry.h"
+#include "MidiPortRegistry.h"
+
+#include "midi.h"
+
+using android::IBinder;
+using android::BBinder;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::Status;
+using android::media::midi::BpMidiDeviceServer;
+using android::media::midi::MidiDeviceInfo;
+using android::media::midi::MidiDeviceRegistry;
+using android::media::midi::MidiPortRegistry;
+
+#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE
+
+/* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
+ *
+ * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
+ * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
+ * ^ +--------------------+-----------------------+
+ * | ^ ^
+ * | | |
+ * | | + timestamp (8 bytes)
+ * | |
+ * | + MIDI data bytes (numBytes bytes)
+ * |
+ * + OpCode (AMIDI_OPCODE_DATA)
+ *
+ * NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
+ * SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
+ * boundaries, and delivers messages in the order that they were sent.
+ * So 'read()' always returns a whole message.
+ */
+
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr) {
+ return MidiDeviceRegistry::getInstance().obtainDeviceToken(id, devicePtr);
+}
+
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_getDeviceInfo bad device token %d: %d", device, result);
+ return result;
+ }
+
+ MidiDeviceInfo deviceInfo;
+ Status txResult = deviceServer->getDeviceInfo(&deviceInfo);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ deviceInfoPtr->type = deviceInfo.getType();
+ deviceInfoPtr->uid = deviceInfo.getUid();
+ deviceInfoPtr->isPrivate = deviceInfo.isPrivate();
+ deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
+ deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+ return OK;
+}
+
+/*
+ * Output (receiving) API
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_openOutputPort bad device token %d: %d", device, result);
+ return result;
+ }
+
+ sp<BBinder> portToken(new BBinder());
+ unique_fd ufd;
+ Status txResult = deviceServer->openOutputPort(portToken, portNumber, &ufd);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_openOutputPort transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ result = MidiPortRegistry::getInstance().addOutputPort(
+ device, portToken, std::move(ufd), outputPortPtr);
+ if (result != OK) {
+ ALOGE("AMIDI_openOutputPort port registration error: %d", result);
+ // Close port
+ return result;
+ }
+ return OK;
+}
+
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages) {
+ unique_fd *ufd;
+ // TODO: May return a nicer self-unlocking object
+ status_t result = MidiPortRegistry::getInstance().getOutputPortFdAndLock(outputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ ssize_t messagesRead = 0;
+ while (messagesRead < maxMessages) {
+ struct pollfd checkFds[1] = { { *ufd, POLLIN, 0 } };
+ int pollResult = poll(checkFds, 1, 0);
+ if (pollResult < 1) {
+ result = android::INVALID_OPERATION;
+ break;
+ }
+
+ AMIDI_Message *message = &messages[messagesRead];
+ uint8_t readBuffer[AMIDI_PACKET_SIZE];
+ memset(readBuffer, 0, sizeof(readBuffer));
+ ssize_t readCount = read(*ufd, readBuffer, sizeof(readBuffer));
+ if (readCount == EINTR) {
+ continue;
+ }
+ if (readCount < 1) {
+ result = android::NOT_ENOUGH_DATA;
+ break;
+ }
+
+ // set Packet Format definition at the top of this file.
+ size_t dataSize = 0;
+ message->opcode = readBuffer[0];
+ message->timestamp = 0;
+ if (message->opcode == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
+ dataSize = readCount - AMIDI_PACKET_OVERHEAD;
+ if (dataSize) {
+ memcpy(message->buffer, readBuffer + 1, dataSize);
+ }
+ message->timestamp = *(uint64_t*) (readBuffer + readCount - sizeof(uint64_t));
+ }
+ message->len = dataSize;
+ ++messagesRead;
+ }
+
+ MidiPortRegistry::getInstance().unlockOutputPort(outputPort);
+ return result == OK ? messagesRead : result;
+}
+
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort) {
+ AMIDI_Device device;
+ sp<IBinder> portToken;
+ status_t result =
+ MidiPortRegistry::getInstance().removeOutputPort(outputPort, &device, &portToken);
+ if (result != OK) {
+ return result;
+ }
+
+ sp<BpMidiDeviceServer> deviceServer;
+ result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ return result;
+ }
+
+ Status txResult = deviceServer->closePort(portToken);
+ if (!txResult.isOk()) {
+ return txResult.transactionError();
+ }
+ return OK;
+}
+
+/*
+ * Input (sending) API
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_openInputPort bad device token %d: %d", device, result);
+ return result;
+ }
+
+ sp<BBinder> portToken(new BBinder());
+ unique_fd ufd; // this is the file descriptor of the "receive" port s
+ Status txResult = deviceServer->openInputPort(portToken, portNumber, &ufd);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_openInputPort transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ result = MidiPortRegistry::getInstance().addInputPort(
+ device, portToken, std::move(ufd), inputPortPtr);
+ if (result != OK) {
+ ALOGE("AMIDI_openInputPort port registration error: %d", result);
+ // Close port
+ return result;
+ }
+
+ return OK;
+}
+
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort) {
+ AMIDI_Device device;
+ sp<IBinder> portToken;
+ status_t result = MidiPortRegistry::getInstance().removeInputPort(
+ inputPort, &device, &portToken);
+ if (result != OK) {
+ ALOGE("AMIDI_closeInputPort remove port error: %d", result);
+ return result;
+ }
+
+ sp<BpMidiDeviceServer> deviceServer;
+ result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_closeInputPort can't find device error: %d", result);
+ return result;
+ }
+
+ Status txResult = deviceServer->closePort(portToken);
+ if (!txResult.isOk()) {
+ result = txResult.transactionError();
+ ALOGE("AMIDI_closeInputPort transaction error: %d", result);
+ return result;
+ }
+
+ return OK;
+}
+
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort /*inputPort*/) {
+ return SIZE_MIDIRECEIVEBUFFER;
+}
+
+static ssize_t AMIDI_makeSendBuffer(uint8_t *buffer, uint8_t *data, ssize_t numBytes, uint64_t timestamp) {
+ buffer[0] = AMIDI_OPCODE_DATA;
+ memcpy(buffer + 1, data, numBytes);
+ memcpy(buffer + 1 + numBytes, ×tamp, sizeof(timestamp));
+ return numBytes + AMIDI_PACKET_OVERHEAD;
+}
+
+// Handy debugging function.
+//static void AMIDI_logBuffer(uint8_t *data, size_t numBytes) {
+// for (size_t index = 0; index < numBytes; index++) {
+// ALOGI(" data @%zu [0x%X]", index, data[index]);
+// }
+//}
+
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes) {
+ return AMIDI_sendWithTimestamp(inputPort, buffer, numBytes, 0);
+}
+
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *data,
+ ssize_t numBytes, int64_t timestamp) {
+
+ if (numBytes > SIZE_MIDIRECEIVEBUFFER) {
+ return android::BAD_VALUE;
+ }
+
+ // AMIDI_logBuffer(data, numBytes);
+
+ unique_fd *ufd = NULL;
+ status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ uint8_t writeBuffer[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD];
+ ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp);
+ ssize_t numWritten = write(*ufd, writeBuffer, numTransferBytes);
+
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu",
+ numTransferBytes, numWritten);
+ }
+
+ return numWritten - AMIDI_PACKET_OVERHEAD;
+}
+
+status_t AMIDI_flush(AMIDI_InputPort inputPort) {
+ unique_fd *ufd = NULL;
+ status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ uint8_t opCode = AMIDI_OPCODE_FLUSH;
+ ssize_t numTransferBytes = 1;
+ ssize_t numWritten = write(*ufd, &opCode, numTransferBytes);
+
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMIDI_flush Couldn't write MIDI flush. requested:%zu, written%zu",
+ numTransferBytes, numWritten);
+ return android::INVALID_OPERATION;
+ }
+
+ return OK;
+}
+
diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h
new file mode 100644
index 0000000..717bc66
--- /dev/null
+++ b/media/native/midi/midi.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_H_
+#define ANDROID_MEDIA_MIDI_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+using android::status_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//typedef struct _AMIDI_Device;
+//typedef struct _AMIDI_InputPort;
+//typedef struct _AMIDI_OutputPort;
+//typedef _AMIDI_Device* AMIDI_Device;
+//typedef _AMIDI_InputPort* AMIDI_InputPort;
+//typedef _AMIDI_OutputPort* AMIDI_OutputPort;
+
+typedef int32_t AMIDI_Device;
+typedef int32_t AMIDI_InputPort;
+typedef int32_t AMIDI_OutputPort;
+
+//TODO - Do we want to wrap this stuff in namespace android { namespace media { namespace midi {?
+
+enum {
+ AMIDI_INVALID_HANDLE = -1
+};
+
+enum {
+ AMIDI_OPCODE_DATA = 1,
+ AMIDI_OPCODE_FLUSH = 2,
+ AMIDI_PACKET_SIZE = 1024, /* !!! Currently MidiPortImpl.MAX_PACKET_SIZE !!! */
+ AMIDI_PACKET_OVERHEAD = 9,
+ AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD
+ /* !!! TBD, currently MidiPortImpl.MAX_PACKET_DATA_SIZE !!! */
+};
+
+typedef struct {
+ uint32_t opcode;
+ uint8_t buffer[AMIDI_BUFFER_SIZE];
+ size_t len;
+ int64_t timestamp;
+} AMIDI_Message;
+
+enum {
+ AMIDI_DEVICE_TYPE_USB = 1,
+ AMIDI_DEVICE_TYPE_VIRTUAL = 2,
+ AMIDI_DEVICE_TYPE_BLUETOOTH = 3
+};
+
+typedef struct {
+ int32_t type;
+ int32_t uid;
+ int32_t isPrivate;
+ int32_t inputPortCount;
+ int32_t outputPortCount;
+} AMIDI_DeviceInfo;
+
+/*
+ * Device API
+ */
+/*
+ * Retrieves the native API device token for the specified Java API device ID.
+ *
+ * uid The Java API id of the device.
+ * devicePtr Receives the associated native API token for the device.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr);
+
+/*
+ * Retrieves information for the native MIDI device.
+ *
+ * device The Native API token for the device.
+ * deviceInfoPtr Receives the associated device info.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr);
+
+/*
+ * API for receiving data from the Output port of a device.
+ */
+/*
+ * Opens the output port.
+ *
+ * device Identifies the device.
+ * portNumber Specifies the zero-based port index on the device to open.
+ * outputPortPtr Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr);
+
+/*
+ * Receives any pending MIDI messages (up to the specified maximum number of messages).
+ *
+ * outputPort Identifies the port to receive messages from.
+ * messages Points to an array (size maxMessages) to receive the MIDI messages.
+ * maxMessages The number of messages allocated in the messages array.
+ *
+ * Returns the number of messages received, or a (negative) error code.
+ */
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages);
+
+/*
+ * Closes the output port.
+ *
+ * outputPort The native API port identifier of the port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort);
+
+/*
+ * API for sending data to the Input port of a device.
+ */
+/*
+ * Opens the input port.
+ *
+ * device Identifies the device.
+ * portNumber Specifies the zero-based port index on the device to open.
+ * inputPortPtr Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr);
+
+/*
+ * Returns the maximum number of bytes that can be received in a single MIDI message.
+ */
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort inputPort);
+
+/*
+ * Sends data to the specified input port.
+ *
+ * inputPort The native API identifier of the port to send data to.
+ * buffer Points to the array of bytes containing the data to send.
+ * numBytes Specifies the number of bytes to write.
+ *
+ * Returns The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes);
+
+/*
+ * Sends data to the specified input port with a timestamp.
+ *
+ * inputPort The native API identifier of the port to send data to.
+ * buffer Points to the array of bytes containing the data to send.
+ * numBytes Specifies the number of bytes to write.
+ * timestamp The time stamp to associate with the sent data.
+ *
+ * Returns The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *buffer,
+ ssize_t numBytes, int64_t timestamp);
+
+/*
+ * Sends a message with a 'MIDI flush command code' to the specified port.
+ *
+ * inputPort The native API identifier of the port to send the flush message to.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_flush(AMIDI_InputPort inputPort);
+
+/*
+ * Closes the input port.
+ *
+ * inputPort The native API port identifier of the port.
+ *
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_MEDIA_MIDI_H_ */
diff --git a/media/tests/NativeMidiDemo/Android.mk b/media/tests/NativeMidiDemo/Android.mk
new file mode 100644
index 0000000..6b08f6b
--- /dev/null
+++ b/media/tests/NativeMidiDemo/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := NativeMidiDemo
+
+#LOCAL_SDK_VERSION := current
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
+
+include $(BUILD_PACKAGE)
+
+# Include packages in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/tests/NativeMidiDemo/AndroidManifest.xml b/media/tests/NativeMidiDemo/AndroidManifest.xml
new file mode 100644
index 0000000..322873f
--- /dev/null
+++ b/media/tests/NativeMidiDemo/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.nativemididemo"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application
+ android:allowBackup="false"
+ android:fullBackupContent="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name">
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+ <activity android:name=".NativeMidi"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
new file mode 100644
index 0000000..0969b10
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
@@ -0,0 +1,354 @@
+/*
+ * 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.example.android.nativemididemo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import java.io.IOException;
+
+public class NativeMidi extends Activity
+{
+ private TextView mCallbackStatusTextView;
+ private TextView mJavaMidiStatusTextView;
+ private TextView mMessagesTextView;
+ private RadioGroup mMidiDevicesRadioGroup;
+ private Handler mTimerHandler = new Handler();
+ private boolean mAudioWorks;
+ private final int mMinFramesPerBuffer = 32; // See {min|max}PlaySamples in nativemidi-jni.cpp
+ private final int mMaxFramesPerBuffer = 1000;
+ private int mFramesPerBuffer;
+
+ private TouchableScrollView mMessagesContainer;
+ private MidiManager mMidiManager;
+ private MidiOutputPortSelector mActivePortSelector;
+
+ private Runnable mTimerRunnable = new Runnable() {
+ private long mLastTime;
+ private long mLastPlaybackCounter;
+ private int mLastCallbackRate;
+ private long mLastUntouchedTime;
+
+ @Override
+ public void run() {
+ final long checkIntervalMs = 1000;
+ long currentTime = System.currentTimeMillis();
+ long currentPlaybackCounter = getPlaybackCounter();
+ if (currentTime - mLastTime >= checkIntervalMs) {
+ int callbackRate = Math.round(
+ (float)(currentPlaybackCounter - mLastPlaybackCounter) /
+ ((float)(currentTime - mLastTime) / (float)1000));
+ if (mLastCallbackRate != callbackRate) {
+ mCallbackStatusTextView.setText(
+ "CB: " + callbackRate + " Hz");
+ mLastCallbackRate = callbackRate;
+ }
+ mLastTime = currentTime;
+ mLastPlaybackCounter = currentPlaybackCounter;
+ }
+
+ String[] newMessages = getRecentMessages();
+ if (newMessages != null) {
+ for (String message : newMessages) {
+ mMessagesTextView.append(message);
+ mMessagesTextView.append("\n");
+ }
+ if (!mMessagesContainer.isTouched) {
+ if (mLastUntouchedTime == 0) mLastUntouchedTime = currentTime;
+ if (currentTime - mLastUntouchedTime > 3000) {
+ mMessagesContainer.fullScroll(View.FOCUS_DOWN);
+ }
+ } else {
+ mLastUntouchedTime = 0;
+ }
+ }
+
+ mTimerHandler.postDelayed(this, checkIntervalMs / 4);
+ }
+ };
+
+ private void addMessage(final String message) {
+ mTimerHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mMessagesTextView.append(message);
+ }
+ }, 0);
+ }
+
+ private class MidiOutputPortSelector implements View.OnClickListener {
+ private final MidiDeviceInfo mDeviceInfo;
+ private final int mPortNumber;
+ private MidiDevice mDevice;
+ private MidiOutputPort mOutputPort;
+
+ MidiOutputPortSelector() {
+ mDeviceInfo = null;
+ mPortNumber = -1;
+ }
+
+ MidiOutputPortSelector(MidiDeviceInfo info, int portNumber) {
+ mDeviceInfo = info;
+ mPortNumber = portNumber;
+ }
+
+ MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; }
+
+ @Override
+ public void onClick(View v) {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ if (mDeviceInfo == null) {
+ mActivePortSelector = this;
+ return;
+ }
+ mMidiManager.openDevice(mDeviceInfo, new MidiManager.OnDeviceOpenedListener() {
+ @Override
+ public void onDeviceOpened(MidiDevice device) {
+ if (device == null) {
+ addMessage("! Failed to open MIDI device !\n");
+ } else {
+ mDevice = device;
+ try {
+ mDevice.mirrorToNative();
+ startReadingMidi(mDevice.getInfo().getId(), mPortNumber);
+ } catch (IOException e) {
+ addMessage("! Failed to mirror to native !\n" + e.getMessage() + "\n");
+ }
+
+ mActivePortSelector = MidiOutputPortSelector.this;
+
+ mOutputPort = device.openOutputPort(mPortNumber);
+ mOutputPort.connect(mMidiReceiver);
+ }
+ }
+ }, null);
+ }
+
+ void closePortOnly() {
+ stopReadingMidi();
+ }
+
+ void close() {
+ closePortOnly();
+ try {
+ if (mOutputPort != null) {
+ mOutputPort.close();
+ }
+ } catch (IOException e) {
+ mMessagesTextView.append("! Port close error: " + e + "\n");
+ } finally {
+ mOutputPort = null;
+ }
+ try {
+ if (mDevice != null) {
+ mDevice.close();
+ }
+ } catch (IOException e) {
+ mMessagesTextView.append("! Device close error: " + e + "\n");
+ } finally {
+ mDevice = null;
+ }
+ }
+ }
+
+ private MidiManager.DeviceCallback mMidiDeviceCallback = new MidiManager.DeviceCallback() {
+ @Override
+ public void onDeviceAdded(MidiDeviceInfo info) {
+ Bundle deviceProps = info.getProperties();
+ String deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (deviceName == null) {
+ deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
+ }
+
+ for (MidiDeviceInfo.PortInfo port : info.getPorts()) {
+ if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_OUTPUT) continue;
+ String portName = port.getName();
+ int portNumber = port.getPortNumber();
+ if (portName.length() == 0) portName = "[" + portNumber + "]";
+ portName += "@" + deviceName;
+ RadioButton outputDevice = new RadioButton(NativeMidi.this);
+ outputDevice.setText(portName);
+ outputDevice.setTag(info);
+ outputDevice.setOnClickListener(new MidiOutputPortSelector(info, portNumber));
+ mMidiDevicesRadioGroup.addView(outputDevice);
+ }
+
+ NativeMidi.this.updateKeepScreenOn();
+ }
+
+ @Override
+ public void onDeviceRemoved(MidiDeviceInfo info) {
+ if (mActivePortSelector != null && info.equals(mActivePortSelector.getDeviceInfo())) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ int removeButtonStart = -1, removeButtonCount = 0;
+ final int buttonCount = mMidiDevicesRadioGroup.getChildCount();
+ boolean checked = false;
+ for (int i = 0; i < buttonCount; ++i) {
+ RadioButton button = (RadioButton) mMidiDevicesRadioGroup.getChildAt(i);
+ if (!info.equals(button.getTag())) continue;
+ if (removeButtonStart == -1) removeButtonStart = i;
+ ++removeButtonCount;
+ if (button.isChecked()) checked = true;
+ }
+ if (removeButtonStart != -1) {
+ mMidiDevicesRadioGroup.removeViews(removeButtonStart, removeButtonCount);
+ if (checked) {
+ mMidiDevicesRadioGroup.check(R.id.device_none);
+ }
+ }
+
+ NativeMidi.this.updateKeepScreenOn();
+ }
+ };
+
+ private class JavaMidiReceiver extends MidiReceiver implements Runnable {
+ @Override
+ public void onSend(byte[] data, int offset,
+ int count, long timestamp) throws IOException {
+ mTimerHandler.removeCallbacks(this);
+ mTimerHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mJavaMidiStatusTextView.setText("Java: MSG");
+ }
+ }, 0);
+ mTimerHandler.postDelayed(this, 100);
+ }
+
+ @Override
+ public void run() {
+ mJavaMidiStatusTextView.setText("Java: ---");
+ }
+ }
+
+ private JavaMidiReceiver mMidiReceiver = new JavaMidiReceiver();
+
+ private void updateKeepScreenOn() {
+ if (mMidiDevicesRadioGroup.getChildCount() > 1) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mCallbackStatusTextView = (TextView) findViewById(R.id.callback_status);
+ mJavaMidiStatusTextView = (TextView) findViewById(R.id.java_midi_status);
+ mMessagesTextView = (TextView) findViewById(R.id.messages);
+ mMessagesContainer = (TouchableScrollView) findViewById(R.id.messages_scroll);
+ mMidiDevicesRadioGroup = (RadioGroup) findViewById(R.id.devices);
+ RadioButton deviceNone = (RadioButton) findViewById(R.id.device_none);
+ deviceNone.setOnClickListener(new MidiOutputPortSelector());
+
+ AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+ if (sampleRate == null) sampleRate = "48000";
+ String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ if (framesPerBuffer == null) framesPerBuffer = Integer.toString(mMaxFramesPerBuffer);
+ mFramesPerBuffer = Integer.parseInt(framesPerBuffer);
+ String audioInitResult = initAudio(Integer.parseInt(sampleRate), mFramesPerBuffer);
+ mMessagesTextView.append("Open SL ES init: " + audioInitResult + "\n");
+
+ if (audioInitResult.startsWith("Success")) {
+ mAudioWorks = true;
+ mTimerHandler.postDelayed(mTimerRunnable, 0);
+ mTimerHandler.postDelayed(mMidiReceiver, 0);
+ }
+
+ mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+ mMidiManager.registerDeviceCallback(mMidiDeviceCallback, new Handler());
+ for (MidiDeviceInfo info : mMidiManager.getDevices()) {
+ mMidiDeviceCallback.onDeviceAdded(info);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mAudioWorks) {
+ mTimerHandler.removeCallbacks(mTimerRunnable);
+ pauseAudio();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mAudioWorks) {
+ mTimerHandler.postDelayed(mTimerRunnable, 0);
+ resumeAudio();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ shutdownAudio();
+ super.onDestroy();
+ }
+
+ public void onClearMessages(View v) {
+ mMessagesTextView.setText("");
+ }
+
+ public void onClosePort(View v) {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.closePortOnly();
+ }
+ }
+
+ private native String initAudio(int sampleRate, int playSamples);
+ private native void pauseAudio();
+ private native void resumeAudio();
+ private native void shutdownAudio();
+
+ private native long getPlaybackCounter();
+ private native String[] getRecentMessages();
+
+ private native void startReadingMidi(int deviceId, int portNumber);
+ private native void stopReadingMidi();
+
+ static {
+ System.loadLibrary("nativemidi_jni");
+ }
+}
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
new file mode 100644
index 0000000..645aafa
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
@@ -0,0 +1,32 @@
+package com.example.android.nativemididemo;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class TouchableScrollView extends ScrollView {
+ public boolean isTouched;
+
+ public TouchableScrollView(Context context) {
+ super(context);
+ }
+
+ public TouchableScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isTouched = true;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isTouched = false;
+ break;
+ }
+ return super.onTouchEvent(event);
+ }
+}
diff --git a/media/tests/NativeMidiDemo/jni/Android.mk b/media/tests/NativeMidiDemo/jni/Android.mk
new file mode 100644
index 0000000..69a64bd
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/Android.mk
@@ -0,0 +1,35 @@
+# 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)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := libnativemidi_jni
+
+LOCAL_SRC_FILES := \
+ nativemidi-jni.cpp \
+ messagequeue.cpp
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/media/native
+
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := libOpenSLES libmidi
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.cpp b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
new file mode 100644
index 0000000..ffaef38
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <atomic>
+#include <stdio.h>
+#include <string.h>
+
+#include "messagequeue.h"
+
+namespace nativemididemo {
+
+static const int messageBufferSize = 64 * 1024;
+static char messageBuffer[messageBufferSize];
+static std::atomic_ullong messagesLastWritePosition;
+
+void writeMessage(const char* message)
+{
+ static unsigned long long lastWritePos = 0;
+ size_t messageLen = strlen(message);
+ if (messageLen == 0) return;
+
+ messageLen += 1; // Also count in the null terminator.
+ char buffer[1024];
+ if (messageLen >= messageBufferSize) {
+ snprintf(buffer, sizeof(buffer), "!!! Message too long: %zu bytes !!!", messageLen);
+ message = buffer;
+ messageLen = strlen(message);
+ }
+
+ size_t wrappedWritePos = lastWritePos % messageBufferSize;
+ if (wrappedWritePos + messageLen >= messageBufferSize) {
+ size_t tailLen = messageBufferSize - wrappedWritePos;
+ memset(messageBuffer + wrappedWritePos, 0, tailLen);
+ lastWritePos += tailLen;
+ wrappedWritePos = 0;
+ }
+
+ memcpy(messageBuffer + wrappedWritePos, message, messageLen);
+ lastWritePos += messageLen;
+ messagesLastWritePosition.store(lastWritePos);
+}
+
+static char messageBufferCopy[messageBufferSize];
+
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject)
+{
+ static unsigned long long lastReadPos = 0;
+ const char* overrunMessage = "";
+ size_t messagesCount = 0;
+ jobjectArray result = NULL;
+
+ // First we copy the portion of the message buffer into messageBufferCopy. If after finishing
+ // the copy we notice that the writer has mutated the portion of the buffer that we were
+ // copying, we report an overrun. Afterwards we can safely read messages from the copy.
+ memset(messageBufferCopy, 0, sizeof(messageBufferCopy));
+ unsigned long long lastWritePos = messagesLastWritePosition.load();
+ if (lastWritePos - lastReadPos > messageBufferSize) {
+ overrunMessage = "!!! Message buffer overrun !!!";
+ messagesCount = 1;
+ lastReadPos = lastWritePos;
+ goto create_array;
+ }
+ if (lastWritePos == lastReadPos) return result;
+ if (lastWritePos / messageBufferSize == lastReadPos / messageBufferSize) {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ memcpy(messageBufferCopy + wrappedReadPos,
+ messageBuffer + wrappedReadPos,
+ lastWritePos % messageBufferSize - wrappedReadPos);
+ } else {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ memcpy(messageBufferCopy, messageBuffer, lastWritePos % messageBufferSize);
+ memcpy(messageBufferCopy + wrappedReadPos,
+ messageBuffer + wrappedReadPos,
+ messageBufferSize - wrappedReadPos);
+ }
+ {
+ unsigned long long newLastWritePos = messagesLastWritePosition.load();
+ if (newLastWritePos - lastReadPos > messageBufferSize) {
+ overrunMessage = "!!! Message buffer overrun !!!";
+ messagesCount = 1;
+ lastReadPos = lastWritePos = newLastWritePos;
+ goto create_array;
+ }
+ }
+ // Otherwise we ignore newLastWritePos, since we only have a copy of the buffer
+ // up to lastWritePos.
+
+ for (unsigned long long readPos = lastReadPos; readPos < lastWritePos; ) {
+ size_t messageLen = strlen(messageBufferCopy + (readPos % messageBufferSize));
+ if (messageLen != 0) {
+ readPos += messageLen + 1;
+ messagesCount++;
+ } else {
+ // Skip to the beginning of the buffer.
+ readPos = (readPos / messageBufferSize + 1) * messageBufferSize;
+ }
+ }
+ if (messagesCount == 0) {
+ lastReadPos = lastWritePos;
+ return result;
+ }
+
+create_array:
+ result = env->NewObjectArray(
+ messagesCount, env->FindClass("java/lang/String"), env->NewStringUTF(overrunMessage));
+ if (lastWritePos == lastReadPos) return result;
+
+ jsize arrayIndex = 0;
+ while (lastReadPos < lastWritePos) {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ if (messageBufferCopy[wrappedReadPos] != '\0') {
+ jstring message = env->NewStringUTF(messageBufferCopy + wrappedReadPos);
+ env->SetObjectArrayElement(result, arrayIndex++, message);
+ lastReadPos += env->GetStringLength(message) + 1;
+ env->DeleteLocalRef(message);
+ } else {
+ // Skip to the beginning of the buffer.
+ lastReadPos = (lastReadPos / messageBufferSize + 1) * messageBufferSize;
+ }
+ }
+ return result;
+}
+
+} // namespace nativemididemo
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.h b/media/tests/NativeMidiDemo/jni/messagequeue.h
new file mode 100644
index 0000000..20aa9e8
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.h
@@ -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.
+ *
+ */
+
+#ifndef NATIVEMIDIDEMO_MESSAGEQUEUE_H
+#define NATIVEMIDIDEMO_MESSAGEQUEUE_H
+
+#include <jni.h>
+
+namespace nativemididemo {
+
+void writeMessage(const char* message);
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject thiz);
+
+} // namespace nativemididemo
+
+#endif // NATIVEMIDIDEMO_MESSAGEQUEUE_H
diff --git a/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
new file mode 100644
index 0000000..8aa874a
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
@@ -0,0 +1,285 @@
+#include <atomic>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <jni.h>
+
+#include <midi/midi.h>
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include "messagequeue.h"
+
+extern "C" {
+JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+ JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+ JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+ JNIEnv* env, jobject thiz);
+}
+
+static const char* errStrings[] = {
+ "SL_RESULT_SUCCESS", // 0
+ "SL_RESULT_PRECONDITIONS_VIOLATED", // 1
+ "SL_RESULT_PARAMETER_INVALID", // 2
+ "SL_RESULT_MEMORY_FAILURE", // 3
+ "SL_RESULT_RESOURCE_ERROR", // 4
+ "SL_RESULT_RESOURCE_LOST", // 5
+ "SL_RESULT_IO_ERROR", // 6
+ "SL_RESULT_BUFFER_INSUFFICIENT", // 7
+ "SL_RESULT_CONTENT_CORRUPTED", // 8
+ "SL_RESULT_CONTENT_UNSUPPORTED", // 9
+ "SL_RESULT_CONTENT_NOT_FOUND", // 10
+ "SL_RESULT_PERMISSION_DENIED", // 11
+ "SL_RESULT_FEATURE_UNSUPPORTED", // 12
+ "SL_RESULT_INTERNAL_ERROR", // 13
+ "SL_RESULT_UNKNOWN_ERROR", // 14
+ "SL_RESULT_OPERATION_ABORTED", // 15
+ "SL_RESULT_CONTROL_LOST" }; // 16
+static const char* getSLErrStr(int code) {
+ return errStrings[code];
+}
+
+static SLObjectItf engineObject;
+static SLEngineItf engineEngine;
+static SLObjectItf outputMixObject;
+static SLObjectItf playerObject;
+static SLPlayItf playerPlay;
+static SLAndroidSimpleBufferQueueItf playerBufferQueue;
+
+static const int minPlaySamples = 32;
+static const int maxPlaySamples = 1000;
+static std::atomic_int playSamples(maxPlaySamples);
+static short playBuffer[maxPlaySamples];
+
+static std::atomic_ullong sharedCounter;
+
+static AMIDI_Device midiDevice = AMIDI_INVALID_HANDLE;
+static std::atomic<AMIDI_OutputPort> midiOutputPort(AMIDI_INVALID_HANDLE);
+
+static int setPlaySamples(int newPlaySamples)
+{
+ if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples;
+ if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples;
+ playSamples.store(newPlaySamples);
+ return newPlaySamples;
+}
+
+// Amount of messages we are ready to handle during one callback cycle.
+static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
+// Static allocation to save time in the callback.
+static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
+
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
+{
+ sharedCounter++;
+
+ AMIDI_OutputPort outputPort = midiOutputPort.load();
+ if (outputPort != AMIDI_INVALID_HANDLE) {
+ char midiDumpBuffer[1024];
+ ssize_t midiReceived = AMIDI_receive(
+ outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
+ if (midiReceived >= 0) {
+ for (ssize_t i = 0; i < midiReceived; ++i) {
+ AMIDI_Message* msg = &incomingMidiMessages[i];
+ if (msg->opcode == AMIDI_OPCODE_DATA) {
+ memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
+ int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+ "%" PRIx64 " ", msg->timestamp);
+ for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
+ pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
+ "%02x ", *b);
+ }
+ nativemididemo::writeMessage(midiDumpBuffer);
+ } else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
+ nativemididemo::writeMessage("MIDI flush");
+ }
+ }
+ } else {
+ snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+ "! MIDI Receive error: %s !", strerror(-midiReceived));
+ nativemididemo::writeMessage(midiDumpBuffer);
+ }
+ }
+
+ size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]);
+ if (usedBufferSize > sizeof(playBuffer)) {
+ usedBufferSize = sizeof(playBuffer);
+ }
+ (*bq)->Enqueue(bq, playBuffer, usedBufferSize);
+}
+
+jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+ JNIEnv* env, jobject, jint sampleRate, jint playSamples) {
+ const char* stage;
+ SLresult result;
+ char printBuffer[1024];
+
+ playSamples = setPlaySamples(playSamples);
+
+ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; }
+
+ result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; }
+
+ result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
+ if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; }
+
+ result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; }
+
+ result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; }
+
+ {
+ SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000,
+ SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN };
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
+ { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
+ SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+ SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
+ SLDataSink audioSnk = { &loc_outmix, NULL };
+ const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
+ const SLboolean req[1] = { SL_BOOLEAN_TRUE };
+ result = (*engineEngine)->CreateAudioPlayer(
+ engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req);
+ if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; }
+
+ result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; }
+ }
+
+ result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
+ if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; }
+
+ result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
+ if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; }
+
+ result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; }
+
+ result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+ if (SL_RESULT_SUCCESS != result) {
+ stage = "enqueue into PlayerBufferQueue"; goto handle_error; }
+
+ result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+ if (SL_RESULT_SUCCESS != result) {
+ stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; }
+
+ snprintf(printBuffer, sizeof(printBuffer),
+ "Success, sample rate %d, buffer samples %d", sampleRate, playSamples);
+ return env->NewStringUTF(printBuffer);
+
+handle_error:
+ snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
+ return env->NewStringUTF(printBuffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+ JNIEnv*, jobject) {
+ if (playerPlay != NULL) {
+ (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
+ }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+ JNIEnv*, jobject) {
+ if (playerBufferQueue != NULL && playerPlay != NULL) {
+ (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+ (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+ }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+ JNIEnv*, jobject) {
+ if (playerObject != NULL) {
+ (*playerObject)->Destroy(playerObject);
+ playerObject = NULL;
+ playerPlay = NULL;
+ playerBufferQueue = NULL;
+ }
+
+ if (outputMixObject != NULL) {
+ (*outputMixObject)->Destroy(outputMixObject);
+ outputMixObject = NULL;
+ }
+
+ if (engineObject != NULL) {
+ (*engineObject)->Destroy(engineObject);
+ engineObject = NULL;
+ engineEngine = NULL;
+ }
+}
+
+jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
+ return sharedCounter.load();
+}
+
+jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+ JNIEnv* env, jobject thiz) {
+ return nativemididemo::getRecentMessagesForJava(env, thiz);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+ JNIEnv*, jobject, jint deviceId, jint portNumber) {
+ char buffer[1024];
+
+ int result = AMIDI_getDeviceById(deviceId, &midiDevice);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result);
+ }
+ nativemididemo::writeMessage(buffer);
+ if (result) return;
+
+ AMIDI_DeviceInfo deviceInfo;
+ result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O",
+ deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate,
+ (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result);
+ }
+ nativemididemo::writeMessage(buffer);
+ if (result) return;
+
+ AMIDI_OutputPort outputPort;
+ result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Opened port %d: token %d", portNumber, outputPort);
+ midiOutputPort.store(outputPort);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not open port %d: %d", deviceId, result);
+ }
+ nativemididemo::writeMessage(buffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+ JNIEnv*, jobject) {
+ AMIDI_OutputPort outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
+ if (outputPort == AMIDI_INVALID_HANDLE) return;
+ int result = AMIDI_closeOutputPort(outputPort);
+ char buffer[1024];
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Closed port by token %d", outputPort);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not close port by token %d: %d", outputPort, result);
+ }
+ nativemididemo::writeMessage(buffer);
+}
diff --git a/media/tests/NativeMidiDemo/res/layout/main.xml b/media/tests/NativeMidiDemo/res/layout/main.xml
new file mode 100644
index 0000000..465d471
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/layout/main.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <TextView
+ android:id="@+id/callback_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ android:text=" | "
+ />
+ <TextView
+ android:id="@+id/java_midi_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ <TextView
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ android:text=" "
+ />
+ </LinearLayout>
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="6sp"
+ android:paddingBottom="6sp"
+ >
+ <RadioGroup
+ android:id="@+id/devices"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:checkedButton="@+id/device_none"
+ >
+ <RadioButton
+ android:id="@+id/device_none"
+ android:text="None"
+ />
+ </RadioGroup>
+ </HorizontalScrollView>
+ <com.example.android.nativemididemo.TouchableScrollView android:id="@+id/messages_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ >
+ <TextView android:id="@+id/messages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ </com.example.android.nativemididemo.TouchableScrollView>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <Button android:id="@+id/clear_messages"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/clear_messages"
+ android:onClick="onClearMessages"
+ />
+ <Button android:id="@+id/close_port"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/close_port"
+ android:onClick="onClosePort"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/values/strings.xml b/media/tests/NativeMidiDemo/res/values/strings.xml
new file mode 100644
index 0000000..5b69b52
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">NativeMidiDemo</string>
+ <string name="clear_messages">Clear Messages</string>
+ <string name="close_port">Close Port</string>
+</resources>
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3ede2ee..1b1f28c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -139,16 +139,27 @@
ANativeActivity_setWindowFlags;
ANativeActivity_setWindowFormat;
ANativeActivity_showSoftInput;
- ANativeWindow_acquire; # removed=26
- ANativeWindow_fromSurface; # removed=26
- ANativeWindow_fromSurfaceTexture; # removed=26
- ANativeWindow_getFormat; # removed=26
- ANativeWindow_getHeight; # removed=26
- ANativeWindow_getWidth; # removed=26
- ANativeWindow_lock; # removed=26
- ANativeWindow_release; # removed=26
- ANativeWindow_setBuffersGeometry; # removed=26
- ANativeWindow_unlockAndPost; # removed=26
+ AHardwareBuffer_acquire; # introduced=26
+ AHardwareBuffer_allocate; # introduced=26
+ AHardwareBuffer_describe; # introduced=26
+ AHardwareBuffer_fromHardwareBuffer; # introduced=26
+ AHardwareBuffer_getNativeHandle; # introduced=26
+ AHardwareBuffer_lock; # introduced=26
+ AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26
+ AHardwareBuffer_release; # introduced=26
+ AHardwareBuffer_sendHandleToUnixSocket; # introduced=26
+ AHardwareBuffer_toHardwareBuffer; # introduced=26
+ AHardwareBuffer_unlock; # introduced=26
+ ANativeWindow_acquire;
+ ANativeWindow_fromSurface;
+ ANativeWindow_fromSurfaceTexture; # introduced-arm=13 introduced-mips=13 introduced-x86=13
+ ANativeWindow_getFormat;
+ ANativeWindow_getHeight;
+ ANativeWindow_getWidth;
+ ANativeWindow_lock;
+ ANativeWindow_release;
+ ANativeWindow_setBuffersGeometry;
+ ANativeWindow_unlockAndPost;
AObbInfo_delete;
AObbInfo_getFlags;
AObbInfo_getPackageName;
diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp
index 6d2de98..bf5cabb 100644
--- a/native/graphics/jni/bitmap.cpp
+++ b/native/graphics/jni/bitmap.cpp
@@ -15,11 +15,7 @@
*/
#include <android/bitmap.h>
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#include <GraphicsJNI.h>
-#pragma GCC diagnostic pop
+#include <Bitmap.h>
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
AndroidBitmapInfo* info) {
@@ -27,32 +23,8 @@
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
- SkBitmap bm;
- GraphicsJNI::getSkBitmap(env, jbitmap, &bm);
-
if (info) {
- info->width = bm.width();
- info->height = bm.height();
- info->stride = bm.rowBytes();
- info->flags = 0;
-
- switch (bm.colorType()) {
- case kN32_SkColorType:
- info->format = ANDROID_BITMAP_FORMAT_RGBA_8888;
- break;
- case kRGB_565_SkColorType:
- info->format = ANDROID_BITMAP_FORMAT_RGB_565;
- break;
- case kARGB_4444_SkColorType:
- info->format = ANDROID_BITMAP_FORMAT_RGBA_4444;
- break;
- case kAlpha_8_SkColorType:
- info->format = ANDROID_BITMAP_FORMAT_A_8;
- break;
- default:
- info->format = ANDROID_BITMAP_FORMAT_NONE;
- break;
- }
+ android::bitmap::imageInfo(env, jbitmap, info);
}
return ANDROID_BITMAP_RESULT_SUCCESS;
}
@@ -62,19 +34,11 @@
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
- SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap);
- if (!pixelRef) {
+ void* addr = android::bitmap::lockPixels(env, jbitmap);
+ if (!addr) {
return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
}
- pixelRef->lockPixels();
- void* addr = pixelRef->pixels();
- if (NULL == addr) {
- pixelRef->unlockPixels();
- pixelRef->unref();
- return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED;
- }
-
if (addrPtr) {
*addrPtr = addr;
}
@@ -86,26 +50,10 @@
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
- SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap);
- if (!pixelRef) {
+ bool unlocked = android::bitmap::unlockPixels(env, jbitmap);
+ if (!unlocked) {
return ANDROID_BITMAP_RESULT_JNI_EXCEPTION;
}
-
- // notifyPixelsChanged() needs be called to apply writes to GL-backed
- // bitmaps. Note that this will slow down read-only accesses to the
- // bitmaps, but the NDK methods are primarily intended to be used for
- // writes.
- pixelRef->notifyPixelsChanged();
-
- pixelRef->unlockPixels();
- // Awkward in that we need to double-unref as the call to get the SkPixelRef
- // did a ref(), so we need to unref() for the local ref and for the previous
- // AndroidBitmap_lockPixels(). However this keeps GraphicsJNI a bit safer
- // if others start using it without knowing about android::Bitmap's "fun"
- // ref counting mechanism(s).
- pixelRef->unref();
- pixelRef->unref();
-
return ANDROID_BITMAP_RESULT_SUCCESS;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java
index 84745b2..19c2c14 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java
@@ -46,8 +46,8 @@
final Uri uri = DocumentsContract.buildRootUri(
MtpDocumentsProvider.AUTHORITY, deviceRootId);
- final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
- intent.setData(uri);
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
intent.addCategory(Intent.CATEGORY_DEFAULT);
this.startActivity(intent);
} catch (IOException exception) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7f7249f0..b06b032 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -28,6 +28,7 @@
import android.util.Log;
import com.android.settingslib.R;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +36,13 @@
public class AppUtils {
private static final String TAG = "AppUtils";
+ /**
+ * This should normally only be set in robolectric tests, to avoid getting a method not found
+ * exception when calling the isInstantApp method of the ApplicationInfo class, because
+ * robolectric does not yet have an implementation of it.
+ */
+ private static InstantAppDataProvider sInstantAppDataProvider = null;
+
public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry,
IUsbManager usbManager, PackageManager pm, Context context) {
String packageName = appEntry.info.packageName;
@@ -74,7 +82,11 @@
* Returns a boolean indicating whether the given package should be considered an instant app
*/
public static boolean isInstant(ApplicationInfo info) {
- if (info.isInstantApp()) {
+ if (sInstantAppDataProvider != null) {
+ if (sInstantAppDataProvider.isInstantApp(info)) {
+ return true;
+ }
+ } else if (info.isInstantApp()) {
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
new file mode 100644
index 0000000..ec8e956
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.applications;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.permission.RuntimePermissionPresentationInfo;
+import android.content.pm.permission.RuntimePermissionPresenter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PermissionsSummaryHelper {
+
+ public static void getPermissionSummary(Context context, String pkg,
+ final PermissionsResultCallback callback) {
+ final RuntimePermissionPresenter presenter =
+ RuntimePermissionPresenter.getInstance(context);
+ presenter.getAppPermissions(pkg, new RuntimePermissionPresenter.OnResultCallback() {
+ @Override
+ public void onGetAppPermissions(
+ @NonNull List<RuntimePermissionPresentationInfo> permissions) {
+ final int permissionCount = permissions.size();
+
+ int grantedStandardCount = 0;
+ int grantedAdditionalCount = 0;
+ int requestedCount = 0;
+ List<CharSequence> grantedStandardLabels = new ArrayList<>();
+
+ for (int i = 0; i < permissionCount; i++) {
+ RuntimePermissionPresentationInfo permission = permissions.get(i);
+ requestedCount++;
+ if (permission.isGranted()) {
+ if (permission.isStandard()) {
+ grantedStandardLabels.add(permission.getLabel());
+ grantedStandardCount++;
+ } else {
+ grantedAdditionalCount++;
+ }
+ }
+ }
+
+ Collator collator = Collator.getInstance();
+ collator.setStrength(Collator.PRIMARY);
+ Collections.sort(grantedStandardLabels, collator);
+
+ callback.onPermissionSummaryResult(grantedStandardCount, requestedCount,
+ grantedAdditionalCount, grantedStandardLabels);
+ }
+ }, null);
+ }
+
+ public static abstract class PermissionsResultCallback {
+ public void onAppWithPermissionsCountsResult(int standardGrantedPermissionAppCount,
+ int standardUsedPermissionAppCount) {
+ /* do nothing - stub */
+ }
+
+ public void onPermissionSummaryResult(int standardGrantedPermissionCount,
+ int requestedPermissionCount, int additionalGrantedPermissionCount,
+ List<CharSequence> grantedGroupLabels) {
+ /* do nothing - stub */
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/instantapps/InstantAppDataProvider.java b/packages/SettingsLib/src/com/android/settingslib/applications/instantapps/InstantAppDataProvider.java
new file mode 100644
index 0000000..8b15715
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/instantapps/InstantAppDataProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.applications.instantapps;
+
+import android.content.pm.ApplicationInfo;
+
+/**
+ * This helps deal with the fact that robolectric does not yet have an implementation of the
+ * isInstantApp method of ApplicationInfo, so we get a method not found exception when running tests
+ * if we try to call it directly.
+ */
+public interface InstantAppDataProvider {
+ public boolean isInstantApp(ApplicationInfo info);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 3df7e66..50972c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -223,7 +223,8 @@
}
final Context context = getContext();
- int level = WifiManager.calculateSignalLevel(mAccessPoint.getRssi(), 5 /* levels */);
+ int level = WifiManager.calculateSignalLevel(
+ mAccessPoint.getRssi(), WifiManager.RSSI_LEVELS);
int wifiBadge = mAccessPoint.getBadge();
if (level != mLevel || wifiBadge != mWifiBadge) {
mLevel = level;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 56ae618..de79d3f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -143,6 +143,8 @@
@GuardedBy("sLock")
private static Signature sSystemSignature;
+ private final Object mWriteLock = new Object();
+
private final Object mLock;
private final Handler mHandler;
@@ -551,62 +553,66 @@
}
private void doWriteState() {
- if (DEBUG_PERSISTENCE) {
- Slog.i(LOG_TAG, "[PERSIST START]");
- }
-
- AtomicFile destination = new AtomicFile(mStatePersistFile);
-
- final int version;
- final ArrayMap<String, Setting> settings;
-
- synchronized (mLock) {
- version = mVersion;
- settings = new ArrayMap<>(mSettings);
- mDirty = false;
- mWriteScheduled = false;
- }
-
- FileOutputStream out = null;
- try {
- out = destination.startWrite();
-
- XmlSerializer serializer = Xml.newSerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_SETTINGS);
- serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
-
- final int settingCount = settings.size();
- for (int i = 0; i < settingCount; i++) {
- Setting setting = settings.valueAt(i);
-
- writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
- setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
- setting.getTag(), setting.isDefaultFromSystem());
-
- if (DEBUG_PERSISTENCE) {
- Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
- }
+ synchronized (mWriteLock) {
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSIST START]");
}
- serializer.endTag(null, TAG_SETTINGS);
- serializer.endDocument();
- destination.finishWrite(out);
+ AtomicFile destination = new AtomicFile(mStatePersistFile);
+
+ final int version;
+ final ArrayMap<String, Setting> settings;
synchronized (mLock) {
- addHistoricalOperationLocked(HISTORICAL_OPERATION_PERSIST, null);
+ version = mVersion;
+ settings = new ArrayMap<>(mSettings);
+ mDirty = false;
+ mWriteScheduled = false;
}
- if (DEBUG_PERSISTENCE) {
- Slog.i(LOG_TAG, "[PERSIST END]");
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_SETTINGS);
+ serializer.attribute(null, ATTR_VERSION, String.valueOf(version));
+
+ final int settingCount = settings.size();
+ for (int i = 0; i < settingCount; i++) {
+ Setting setting = settings.valueAt(i);
+
+ writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
+ setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
+ setting.getTag(), setting.isDefaultFromSystem());
+
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
+ + setting.getValue());
+ }
+ }
+
+ serializer.endTag(null, TAG_SETTINGS);
+ serializer.endDocument();
+ destination.finishWrite(out);
+
+ synchronized (mLock) {
+ addHistoricalOperationLocked(HISTORICAL_OPERATION_PERSIST, null);
+ }
+
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSIST END]");
+ }
+ } catch (Throwable t) {
+ Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
+ destination.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
}
- } catch (Throwable t) {
- Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
- destination.failWrite(out);
- } finally {
- IoUtils.closeQuietly(out);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
index ddee89e..674ed5a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
@@ -19,7 +19,7 @@
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.annotations.ProvidesInterface;
-@ProvidesInterface(action = NavGesture.ACTION, version = NavBarButtonProvider.VERSION)
+@ProvidesInterface(action = NavGesture.ACTION, version = NavGesture.VERSION)
public interface NavGesture extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_GESTURE";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
index 6623cabe..b5093b3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -43,13 +43,6 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mStatusBar != null) {
- mStatusBar.onConfigurationChanged(newConfig);
- }
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mStatusBar != null) {
mStatusBar.dump(fd, pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ae402ef..e7256d1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -71,8 +71,15 @@
}
@Override
+ public void onPinnedStackAnimationStarted() {
+ // Disable touches while the animation is running
+ mTouchHandler.setTouchEnabled(false);
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() {
- // TODO(winsonc): Disable touch interaction with the PiP until the animation ends
+ // Re-enable touches after the animation completes
+ mTouchHandler.setTouchEnabled(true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index d832810..4100b66 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -181,6 +181,10 @@
registerInputConsumer();
}
+ public void setTouchEnabled(boolean enabled) {
+ mTouchState.setAllowTouches(enabled);
+ }
+
public void onActivityPinned() {
// Reset some states once we are pinned
if (mIsTappingThrough) {
@@ -294,6 +298,7 @@
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
+ mTouchState.reset();
break;
}
}
@@ -418,6 +423,10 @@
@Override
public void onDown(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return;
+ }
+
if (ENABLE_DRAG_TO_DISMISS) {
mDismissViewController.createDismissTarget();
mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
@@ -426,6 +435,10 @@
@Override
boolean onMove(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
if (touchState.startedDragging()) {
mSavedSnapFraction = -1f;
}
@@ -458,6 +471,10 @@
@Override
public boolean onUp(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
try {
if (ENABLE_DRAG_TO_DISMISS) {
mHandler.removeCallbacks(mShowDismissAffordance);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 702ad0a..a317dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -37,6 +37,7 @@
private final PointF mLastTouch = new PointF();
private final PointF mLastDelta = new PointF();
private final PointF mVelocity = new PointF();
+ private boolean mAllowTouches = true;
private boolean mIsUserInteracting = false;
private boolean mIsDragging = false;
private boolean mStartedDragging = false;
@@ -48,23 +49,41 @@
}
/**
+ * Resets this state.
+ */
+ public void reset() {
+ mAllowDraggingOffscreen = false;
+ mIsDragging = false;
+ mStartedDragging = false;
+ mIsUserInteracting = false;
+ }
+
+ /**
* Processess a given touch event and updates the state.
*/
public void onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
+ if (!mAllowTouches) {
+ return;
+ }
+
// Initialize the velocity tracker
initOrResetVelocityTracker();
+
mActivePointerId = ev.getPointerId(0);
mLastTouch.set(ev.getX(), ev.getY());
mDownTouch.set(mLastTouch);
- mIsDragging = false;
- mStartedDragging = false;
mAllowDraggingOffscreen = true;
mIsUserInteracting = true;
break;
}
case MotionEvent.ACTION_MOVE: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -86,6 +105,11 @@
break;
}
case MotionEvent.ACTION_POINTER_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
@@ -100,6 +124,11 @@
break;
}
case MotionEvent.ACTION_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000,
@@ -112,7 +141,6 @@
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
- mIsUserInteracting = false;
recycleVelocityTracker();
break;
}
@@ -171,6 +199,19 @@
}
/**
+ * Sets whether touching is currently allowed.
+ */
+ public void setAllowTouches(boolean allowTouches) {
+ mAllowTouches = allowTouches;
+
+ // If the user happens to touch down before this is sent from the system during a transition
+ // then block any additional handling by resetting the state now
+ if (mIsUserInteracting) {
+ reset();
+ }
+ }
+
+ /**
* Disallows dragging offscreen for the duration of the current gesture.
*/
public void setDisallowDraggingOffscreen() {
@@ -202,6 +243,7 @@
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 428fe9b..7d13f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -84,7 +84,6 @@
mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label);
mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock);
- mLabelContainer.setBackground(newTileBackground());
addView(mLabelContainer);
}
@@ -102,6 +101,11 @@
mLabel.setText(state.label);
}
mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE);
+ if (state.dualTarget != mLabelContainer.isClickable()) {
+ mLabelContainer.setClickable(state.dualTarget);
+ mLabelContainer.setLongClickable(state.dualTarget);
+ mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null);
+ }
mLabel.setEnabled(!state.disabledByPolicy);
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
}
@@ -110,7 +114,9 @@
public void init(OnClickListener click, OnClickListener secondaryClick,
OnLongClickListener longClick) {
super.init(click, secondaryClick, longClick);
- mLabelContainer.setClickable(true);
mLabelContainer.setOnClickListener(secondaryClick);
+ mLabelContainer.setOnLongClickListener(longClick);
+ mLabelContainer.setClickable(false);
+ mLabelContainer.setLongClickable(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index b28b0e7..a76299d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -288,7 +288,7 @@
drawable = mTile.getIcon().loadDrawable(mContext);
} catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state");
- tileState = Tile.STATE_UNAVAILABLE;
+ state.state = Tile.STATE_UNAVAILABLE;
drawable = mDefaultIcon.loadDrawable(mContext);
}
state.icon = new DrawableIcon(drawable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index f7bfc1e..62aa466 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -23,16 +23,18 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.RelativeSizeSpan;
+import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.Checkable;
import android.widget.ImageView;
+import android.widget.Switch;
import android.widget.TextView;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.BatteryInfo;
import com.android.settingslib.graph.BatteryMeterDrawableBase;
@@ -43,9 +45,8 @@
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BatteryController;
-import java.text.NumberFormat;
-
-public class BatteryTile extends QSTile<QSTile.State> implements BatteryController.BatteryStateChangeCallback {
+public class BatterySaverTile extends QSTile<QSTile.BooleanState> implements
+ BatteryController.BatteryStateChangeCallback {
private final BatteryController mBatteryController;
private final BatteryDetail mBatteryDetail = new BatteryDetail();
@@ -56,19 +57,14 @@
private boolean mDetailShown;
private boolean mPluggedIn;
- public BatteryTile(Host host) {
+ public BatterySaverTile(Host host) {
super(host);
mBatteryController = Dependency.get(BatteryController.class);
}
@Override
- public State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- public DetailAdapter getDetailAdapter() {
- return mBatteryDetail;
+ public BooleanState newTileState() {
+ return new BooleanState();
}
@Override
@@ -109,22 +105,15 @@
}
@Override
- protected void handleUpdateState(State state, Object arg) {
- int level = (arg != null) ? (Integer) arg : mLevel;
- String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
-
+ protected void handleUpdateState(BooleanState state, Object arg) {
state.state = mCharging ? Tile.STATE_UNAVAILABLE
: mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver);
state.label = mContext.getString(R.string.battery_detail_switch_title);
- state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_battery,
- percentage) + "," +
- (mPowerSave ? mContext.getString(R.string.battery_saver_notification_title)
- : mCharging ? mContext.getString(R.string.expanded_header_battery_charging)
- : "")
- + "," + mContext.getString(R.string.accessibility_battery_details);
+ state.contentDescription = state.label;
+ state.value = mPowerSave;
state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
- = Button.class.getName();
+ = Switch.class.getName();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cda902b..1042356 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -154,6 +154,7 @@
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned() { }
public void onPinnedActivityRestartAttempt(String launchedFromPackage) { }
+ public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
public void onActivityForcedResizable(String packageName, int taskId) { }
public void onActivityDismissingDockedStack() { }
@@ -206,6 +207,12 @@
}
@Override
+ public void onPinnedStackAnimationStarted() throws RemoteException {
+ mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() throws RemoteException {
mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
@@ -1219,6 +1226,7 @@
private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
private static final int ON_TASK_PROFILE_LOCKED = 8;
+ private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
@Override
public void handleMessage(Message msg) {
@@ -1248,6 +1256,12 @@
}
break;
}
+ case ON_PINNED_STACK_ANIMATION_STARTED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+ }
+ break;
+ }
case ON_PINNED_STACK_ANIMATION_ENDED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 98787f7..d4997ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -923,12 +923,6 @@
mOnActivatedListener = onActivatedListener;
}
- public void reset() {
- setTintColor(0);
- resetBackgroundAlpha();
- setBelowSpeedBump(false);
- }
-
public boolean hasSameBgColor(ActivatableNotificationView otherView) {
return calculateBgColor() == otherView.calculateBgColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index bfe4bb2..602c3df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.notification.NotificationInflater.InflationExceptionHandler;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -42,6 +44,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Chronometer;
import android.widget.ImageView;
+import android.widget.RemoteViews;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,6 +55,8 @@
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -70,6 +75,7 @@
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+ private final NotificationInflater mNotificationInflater;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mNotificationMinHeightLegacy;
@@ -152,14 +158,15 @@
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
- if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
+ if (!mShowingPublic && (!mIsLowPriority || isExpanded())
+ && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
mGroupExpansionChanging = true;
final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification);
mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
nowExpanded);
- logExpansionEvent(true /* userAction */, wasExpanded);
+ onExpansionChanged(true /* userAction */, wasExpanded);
} else {
if (v.isAccessibilityFocused()) {
mPrivateLayout.setFocusOnVisibilityChange();
@@ -246,6 +253,7 @@
}
if (mIsSummaryWithChildren) {
setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
+ setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView());
List<ExpandableNotificationRow> notificationChildren =
mChildrenContainer.getNotificationChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
@@ -298,17 +306,22 @@
}
}
- public void onNotificationUpdated(NotificationData.Entry entry) {
+ public void updateNotification(NotificationData.Entry entry) throws InflationException {
mEntry = entry;
mStatusBarNotification = entry.notification;
+ mNotificationInflater.inflateNotificationViews();
+ onNotificationUpdated();
+ }
+
+ private void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
- l.onNotificationUpdated(entry);
+ l.onNotificationUpdated(mEntry);
}
mIsColorized = mStatusBarNotification.getNotification().isColorized();
mShowingPublicInitialized = false;
updateNotificationColor();
if (mIsSummaryWithChildren) {
- mChildrenContainer.recreateNotificationHeader(mExpandClickListener, mEntry.notification);
+ mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
mChildrenContainer.onNotificationUpdated();
}
if (mIconAnimationRunning) {
@@ -458,6 +471,9 @@
boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
mNotificationParent = childInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(childInGroup);
+ if (mNotificationInflater.setIsChildInGroup(childInGroup)) {
+ onNotificationUpdated();
+ }
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
@@ -671,9 +687,13 @@
return mPrivateLayout.getNotificationHeader();
}
- private NotificationHeaderView getVisibleNotificationHeader() {
+ /**
+ * @return the currently visible notification header. This can be different from
+ * {@link #getNotificationHeader()} in case it is a low-priority group.
+ */
+ public NotificationHeaderView getVisibleNotificationHeader() {
if (mIsSummaryWithChildren && !mShowingPublic) {
- return mChildrenContainer.getHeaderView();
+ return mChildrenContainer.getVisibleHeader();
}
return getShowingLayout().getVisibleNotificationHeader();
}
@@ -711,7 +731,7 @@
}
}
- public void reInflateViews() {
+ public void onDensityOrFontScaleChanged() {
initDimens();
if (mIsSummaryWithChildren) {
if (mChildrenContainer != null) {
@@ -742,6 +762,8 @@
for (NotificationContentView l : mLayouts) {
l.reInflateViews();
}
+ mNotificationInflater.onDensityOrFontScaleChanged();
+ onNotificationUpdated();
}
public void setContentBackground(int customBackgroundColor, boolean animate,
@@ -891,7 +913,7 @@
}
public View getNotificationIcon() {
- NotificationHeaderView notificationHeader = getNotificationHeader();
+ NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
if (notificationHeader != null) {
return notificationHeader.getIcon();
}
@@ -1008,17 +1030,32 @@
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
mPrivateLayout.setIsLowPriority(isLowPriority);
+ mNotificationInflater.setIsLowPriority(mIsLowPriority);
if (mChildrenContainer != null) {
mChildrenContainer.setIsLowPriority(isLowPriority);
}
}
+ public boolean isLowPriority() {
+ return mIsLowPriority;
+ }
+
public void setUseIncreasedCollapsedHeight(boolean use) {
mUseIncreasedCollapsedHeight = use;
+ mNotificationInflater.setUsesIncreasedHeight(use);
}
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
+ mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
+ }
+
+ public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
+ }
+
+ public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) {
+ mNotificationInflater.setInflateExceptionHandler(inflateExceptionHandler);
}
public interface ExpansionLogger {
@@ -1028,6 +1065,7 @@
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
mFalsingManager = FalsingManager.getInstance(context);
+ mNotificationInflater = new NotificationInflater(this);
initDimens();
}
@@ -1063,26 +1101,8 @@
/**
* Resets this view so it can be re-used for an updated notification.
*/
- @Override
public void reset() {
- super.reset();
- final boolean wasExpanded = isExpanded();
- mExpandable = false;
- mHasUserChangedExpansion = false;
- mUserLocked = false;
- mShowingPublic = false;
- mSensitive = false;
mShowingPublicInitialized = false;
- mIsSystemExpanded = false;
- mOnKeyguard = false;
- mPublicLayout.reset();
- mPrivateLayout.reset();
- resetHeight();
- resetTranslation();
- logExpansionEvent(false, wasExpanded);
- }
-
- public void resetHeight() {
onHeightReset();
requestLayout();
}
@@ -1124,7 +1144,7 @@
public void onInflate(ViewStub stub, View inflated) {
mChildrenContainer = (NotificationChildrenContainer) inflated;
mChildrenContainer.setIsLowPriority(mIsLowPriority);
- mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
+ mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
mTranslateableViews.add(mChildrenContainer);
}
@@ -1253,9 +1273,7 @@
if (mChildrenContainer != null) {
mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
: INVISIBLE);
- mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren
- ? VISIBLE
- : INVISIBLE);
+ mChildrenContainer.setHeaderVisible(!mShowingPublic && mIsSummaryWithChildren);
}
// The limits might have changed if the view suddenly became a group or vice versa
updateLimits();
@@ -1323,7 +1341,6 @@
*/
public void setUserExpanded(boolean userExpanded) {
setUserExpanded(userExpanded, false /* allowChildExpansion */);
- updateShelfIconColor();
}
/**
@@ -1334,22 +1351,28 @@
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
mFalsingManager.setNotificationExpanded();
- if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) {
+ if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion
+ && !mChildrenContainer.showingAsLowPriority()) {
final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
- logExpansionEvent(true /* userAction */, wasExpanded);
+ onExpansionChanged(true /* userAction */, wasExpanded);
return;
}
if (userExpanded && !mExpandable) return;
final boolean wasExpanded = isExpanded();
mHasUserChangedExpansion = true;
mUserExpanded = userExpanded;
- logExpansionEvent(true, wasExpanded);
+ onExpansionChanged(true /* userAction */, wasExpanded);
}
public void resetUserExpansion() {
+ boolean changed = mUserExpanded;
mHasUserChangedExpansion = false;
mUserExpanded = false;
+ if (changed && mIsSummaryWithChildren) {
+ mChildrenContainer.onExpansionChanged();
+ }
+ updateShelfIconColor();
}
public boolean isUserLocked() {
@@ -1383,9 +1406,8 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- updateShelfIconColor();
notifyHeightChanged(false /* needsAnimation */);
- logExpansionEvent(false, wasExpanded);
+ onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
}
@@ -1399,7 +1421,7 @@
if (onKeyguard != mOnKeyguard) {
final boolean wasExpanded = isExpanded();
mOnKeyguard = onKeyguard;
- logExpansionEvent(false, wasExpanded);
+ onExpansionChanged(false /* userAction */, wasExpanded);
if (wasExpanded != isExpanded()) {
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
@@ -1470,8 +1492,8 @@
mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
- mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
- mEntry.notification);
+ mChildrenContainer.recreateNotificationHeader(mExpandClickListener
+ );
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
@@ -1526,7 +1548,7 @@
* the top.
*/
private void updateContentShiftHeight() {
- NotificationHeaderView notificationHeader = getNotificationHeader();
+ NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
if (notificationHeader != null) {
CachingIconView icon = notificationHeader.getIcon();
mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
@@ -1737,7 +1759,17 @@
@Override
public void setActualHeight(int height, boolean notifyListeners) {
+ boolean changed = height != getActualHeight();
super.setActualHeight(height, notifyListeners);
+ if (changed && isRemoved()) {
+ // TODO: remove this once we found the gfx bug for this.
+ // This is a hack since a removed view sometimes would just stay blank. it occured
+ // when sending yourself a message and then clicking on it.
+ ViewGroup parent = (ViewGroup) getParent();
+ if (parent != null) {
+ parent.invalidate();
+ }
+ }
if (mGuts != null && mGuts.isExposed()) {
mGuts.setActualHeight(height);
return;
@@ -1828,9 +1860,9 @@
return mShowingPublic ? mPublicLayout : mPrivateLayout;
}
- public void setShowingLegacyBackground(boolean showing) {
+ public void setLegacy(boolean legacy) {
for (NotificationContentView l : mLayouts) {
- l.setShowingLegacyBackground(showing);
+ l.setLegacy(legacy);
}
}
@@ -1910,7 +1942,7 @@
if (isGroupExpanded()) {
return 1.0f;
} else if (isUserLocked()) {
- return mChildrenContainer.getGroupExpandFraction();
+ return mChildrenContainer.getIncreasedPaddingAmount();
}
} else if (isColorized() && (!mIsLowPriority || isExpanded())) {
return -1.0f;
@@ -1933,13 +1965,19 @@
return super.disallowSingleClick(event);
}
- private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
+ private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
boolean nowExpanded = isExpanded();
- if (mIsSummaryWithChildren) {
+ if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) {
nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
}
- if (wasExpanded != nowExpanded && mLogger != null) {
- mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
+ if (nowExpanded != wasExpanded) {
+ updateShelfIconColor();
+ if (mLogger != null) {
+ mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
+ }
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.onExpansionChanged();
+ }
}
}
@@ -1980,7 +2018,8 @@
@Override
public boolean isAboveShelf() {
- return mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf);
+ return !isOnKeyguard()
+ && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
}
public void setShowAmbient(boolean showAmbient) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 57d2e1c..74e65fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -80,7 +80,7 @@
private boolean mDark;
private boolean mAnimate;
private boolean mIsHeadsUp;
- private boolean mShowingLegacyBackground;
+ private boolean mLegacy;
private boolean mIsChildInGroup;
private int mSmallHeight;
private int mHeadsUpHeight;
@@ -140,7 +140,6 @@
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
- reset();
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -329,41 +328,6 @@
updateVisibility();
}
- public void reset() {
- mPreviousExpandedRemoteInputIntent = null;
- if (mExpandedRemoteInput != null) {
- mExpandedRemoteInput.onNotificationUpdateOrReset();
- if (mExpandedRemoteInput.isActive()) {
- mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
- mCachedExpandedRemoteInput = mExpandedRemoteInput;
- mExpandedRemoteInput.dispatchStartTemporaryDetach();
- ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
- }
- }
- if (mExpandedChild != null) {
- mExpandedChild.animate().cancel();
- removeView(mExpandedChild);
- mExpandedRemoteInput = null;
- }
- mPreviousHeadsUpRemoteInputIntent = null;
- if (mHeadsUpRemoteInput != null) {
- mHeadsUpRemoteInput.onNotificationUpdateOrReset();
- if (mHeadsUpRemoteInput.isActive()) {
- mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
- mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
- mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
- ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
- }
- }
- if (mHeadsUpChild != null) {
- mHeadsUpChild.animate().cancel();
- removeView(mHeadsUpChild);
- mHeadsUpRemoteInput = null;
- }
- mExpandedChild = null;
- mHeadsUpChild = null;
- }
-
public View getContractedChild() {
return mContractedChild;
}
@@ -394,8 +358,30 @@
public void setExpandedChild(View child) {
if (mExpandedChild != null) {
+ mPreviousExpandedRemoteInputIntent = null;
+ if (mExpandedRemoteInput != null) {
+ mExpandedRemoteInput.onNotificationUpdateOrReset();
+ if (mExpandedRemoteInput.isActive()) {
+ mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
+ mCachedExpandedRemoteInput = mExpandedRemoteInput;
+ mExpandedRemoteInput.dispatchStartTemporaryDetach();
+ ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
+ }
+ }
mExpandedChild.animate().cancel();
removeView(mExpandedChild);
+ mExpandedRemoteInput = null;
+ }
+ if (child == null) {
+ mExpandedChild = null;
+ mExpandedWrapper = null;
+ if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
+ mVisibleType = VISIBLE_TYPE_CONTRACTED;
+ }
+ if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
+ mTransformationStartVisibleType = UNDEFINED;
+ }
+ return;
}
addView(child);
mExpandedChild = child;
@@ -405,8 +391,30 @@
public void setHeadsUpChild(View child) {
if (mHeadsUpChild != null) {
+ mPreviousHeadsUpRemoteInputIntent = null;
+ if (mHeadsUpRemoteInput != null) {
+ mHeadsUpRemoteInput.onNotificationUpdateOrReset();
+ if (mHeadsUpRemoteInput.isActive()) {
+ mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
+ mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
+ mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
+ ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
+ }
+ }
mHeadsUpChild.animate().cancel();
removeView(mHeadsUpChild);
+ mHeadsUpRemoteInput = null;
+ }
+ if (child == null) {
+ mHeadsUpChild = null;
+ mHeadsUpWrapper = null;
+ if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
+ mVisibleType = VISIBLE_TYPE_CONTRACTED;
+ }
+ if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
+ mTransformationStartVisibleType = UNDEFINED;
+ }
+ return;
}
addView(child);
mHeadsUpChild = child;
@@ -623,7 +631,7 @@
}
public int getMinHeight(boolean likeGroupExpanded) {
- if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded() || mIsLowPriority) {
+ if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
return mContractedChild.getHeight();
} else {
return mSingleLineView.getHeight();
@@ -897,7 +905,7 @@
height = mContentHeight;
}
int expandedVisualType = getVisualTypeForHeight(height);
- int collapsedVisualType = mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority
+ int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
? VISIBLE_TYPE_SINGLELINE
: getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
return mTransformationStartVisibleType == collapsedVisualType
@@ -918,7 +926,7 @@
if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
return VISIBLE_TYPE_EXPANDED;
}
- if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority) {
+ if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
return VISIBLE_TYPE_SINGLELINE;
}
@@ -978,25 +986,37 @@
return false;
}
- public void setShowingLegacyBackground(boolean showing) {
- mShowingLegacyBackground = showing;
- updateShowingLegacyBackground();
+ public void setLegacy(boolean legacy) {
+ mLegacy = legacy;
+ updateLegacy();
}
- private void updateShowingLegacyBackground() {
+ private void updateLegacy() {
if (mContractedChild != null) {
- mContractedWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
+ mContractedWrapper.setLegacy(mLegacy);
}
if (mExpandedChild != null) {
- mExpandedWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
+ mExpandedWrapper.setLegacy(mLegacy);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
+ mHeadsUpWrapper.setLegacy(mLegacy);
}
}
public void setIsChildInGroup(boolean isChildInGroup) {
mIsChildInGroup = isChildInGroup;
+ if (mContractedChild != null) {
+ mContractedWrapper.setIsChildInGroup(mIsChildInGroup);
+ }
+ if (mExpandedChild != null) {
+ mExpandedWrapper.setIsChildInGroup(mIsChildInGroup);
+ }
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.setIsChildInGroup(mIsChildInGroup);
+ }
+ if (mAmbientChild != null) {
+ mAmbientWrapper.setIsChildInGroup(mIsChildInGroup);
+ }
updateSingleLineView();
}
@@ -1005,19 +1025,19 @@
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
updateSingleLineView();
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
+ mContractedWrapper.notifyContentUpdated(entry.row);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
+ mExpandedWrapper.notifyContentUpdated(entry.row);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
+ mHeadsUpWrapper.notifyContentUpdated(entry.row);
}
if (mAmbientChild != null) {
- mAmbientWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
+ mAmbientWrapper.notifyContentUpdated(entry.row);
}
applyRemoteInput(entry);
- updateShowingLegacyBackground();
+ updateLegacy();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
mPreviousExpandedRemoteInputIntent = null;
@@ -1176,7 +1196,8 @@
mExpandable = expandable;
// if the expanded child has the same height as the collapsed one we hide it.
if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
- if (!mIsHeadsUp || mHeadsUpChild == null || mContainingNotification.isOnKeyguard()) {
+ if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
+ || mHeadsUpChild == null || mContainingNotification.isOnKeyguard()) {
if (mExpandedChild.getHeight() == mContractedChild.getHeight()) {
expandable = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 81db429..8f8d966 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -34,6 +34,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -65,7 +66,6 @@
public ExpandableNotificationRow row; // the outer expanded view
private boolean interruption;
public boolean autoRedacted; // whether the redacted notification was generated by us
- public boolean legacy; // whether the notification has a legacy, dark background
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public RemoteViews cachedContentView;
@@ -95,118 +95,20 @@
* Resets the notification entry to be re-used.
*/
public void reset() {
- // NOTE: Icon needs to be preserved for now.
- // We should fix this at some point.
- autoRedacted = false;
- legacy = false;
lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
if (row != null) {
row.reset();
}
}
- public View getContentView() {
- return row.getPrivateLayout().getContractedChild();
- }
-
public View getExpandedContentView() {
return row.getPrivateLayout().getExpandedChild();
}
- public View getHeadsUpContentView() {
- return row.getPrivateLayout().getHeadsUpChild();
- }
-
public View getPublicContentView() {
return row.getPublicLayout().getContractedChild();
}
- public View getAmbientContentView() {
- return row.getPrivateLayout().getAmbientChild();
- }
-
- public boolean cacheContentViews(Context ctx, Notification updatedNotification,
- boolean isLowPriority, boolean useIncreasedCollapsedView,
- boolean useIncreasedHeadsUp) {
- boolean applyInPlace = false;
- if (updatedNotification != null) {
- final Notification.Builder updatedNotificationBuilder
- = Notification.Builder.recoverBuilder(ctx, updatedNotification);
- final RemoteViews newContentView = createContentView(updatedNotificationBuilder,
- isLowPriority, useIncreasedCollapsedView);
- final RemoteViews newBigContentView = createBigContentView(
- updatedNotificationBuilder, isLowPriority);
- final RemoteViews newHeadsUpContentView =
- updatedNotificationBuilder.createHeadsUpContentView(useIncreasedHeadsUp);
- final RemoteViews newPublicNotification
- = updatedNotificationBuilder.makePublicContentView();
- final RemoteViews newAmbientNotification
- = updatedNotificationBuilder.makeAmbientNotification();
-
- boolean sameCustomView = Objects.equals(
- notification.getNotification().extras.getBoolean(
- Notification.EXTRA_CONTAINS_CUSTOM_VIEW),
- updatedNotification.extras.getBoolean(
- Notification.EXTRA_CONTAINS_CUSTOM_VIEW));
- applyInPlace = compareRemoteViews(cachedContentView, newContentView)
- && compareRemoteViews(cachedBigContentView, newBigContentView)
- && compareRemoteViews(cachedHeadsUpContentView, newHeadsUpContentView)
- && compareRemoteViews(cachedPublicContentView, newPublicNotification)
- && compareRemoteViews(cachedAmbientContentView, newAmbientNotification)
- && sameCustomView;
- cachedPublicContentView = newPublicNotification;
- cachedHeadsUpContentView = newHeadsUpContentView;
- cachedBigContentView = newBigContentView;
- cachedContentView = newContentView;
- cachedAmbientContentView = newAmbientNotification;
- } else {
- final Notification.Builder builder
- = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
-
- cachedContentView = createContentView(builder, isLowPriority,
- useIncreasedCollapsedView);
- cachedBigContentView = createBigContentView(builder, isLowPriority);
- cachedHeadsUpContentView = builder.createHeadsUpContentView(useIncreasedHeadsUp);
- cachedPublicContentView = builder.makePublicContentView();
- cachedAmbientContentView = builder.makeAmbientNotification();
-
- applyInPlace = false;
- }
- return applyInPlace;
- }
-
- private RemoteViews createBigContentView(Notification.Builder builder,
- boolean isLowPriority) {
- RemoteViews bigContentView = builder.createBigContentView();
- if (bigContentView != null) {
- return bigContentView;
- }
- if (isLowPriority) {
- RemoteViews contentView = builder.createContentView();
- Notification.Builder.makeHeaderExpanded(contentView);
- return contentView;
- }
- return null;
- }
-
- private RemoteViews createContentView(Notification.Builder builder,
- boolean isLowPriority, boolean useLarge) {
- if (isLowPriority) {
- return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
- }
- return builder.createContentView(useLarge);
- }
-
- // Returns true if the RemoteViews are the same.
- private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
- return (a == null && b == null) ||
- (a != null && b != null
- && b.getPackage() != null
- && a.getPackage() != null
- && a.getPackage().equals(b.getPackage())
- && a.getLayoutId() == b.getLayoutId());
- }
-
public void notifyFullScreenIntentLaunched() {
lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
}
@@ -219,13 +121,14 @@
* Create the icons for a notification
* @param context the context to create the icons with
* @param sbn the notification
- * @throws IconException
+ * @throws InflationException
*/
- public void createIcons(Context context, StatusBarNotification sbn) throws IconException {
+ public void createIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
Notification n = sbn.getNotification();
final Icon smallIcon = n.getSmallIcon();
if (smallIcon == null) {
- throw new IconException("No small icon in notification from "
+ throw new InflationException("No small icon in notification from "
+ sbn.getPackageName());
}
@@ -248,7 +151,7 @@
if (!icon.set(ic) || !expandedIcon.set(ic)) {
icon = null;
expandedIcon = null;
- throw new IconException("Couldn't create icon: " + ic);
+ throw new InflationException("Couldn't create icon: " + ic);
}
expandedIcon.setVisibility(View.INVISIBLE);
expandedIcon.setOnVisibilityChangedListener(
@@ -270,9 +173,9 @@
* Update the notification icons.
* @param context the context to create the icons with.
* @param n the notification to read the icon from.
- * @throws IconException
+ * @throws InflationException
*/
- public void updateIcons(Context context, Notification n) throws IconException {
+ public void updateIcons(Context context, Notification n) throws InflationException {
if (icon != null) {
// Update the icon
final StatusBarIcon ic = new StatusBarIcon(
@@ -285,7 +188,7 @@
icon.setNotification(n);
expandedIcon.setNotification(n);
if (!icon.set(ic) || !expandedIcon.set(ic)) {
- throw new IconException("Couldn't update icon: " + ic);
+ throw new InflationException("Couldn't update icon: " + ic);
}
}
}
@@ -612,10 +515,4 @@
public String getCurrentMediaNotificationKey();
public NotificationGroupManager getGroupManager();
}
-
- public static class IconException extends Exception {
- IconException(String error) {
- super(error);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 9703235..af464c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -72,11 +72,6 @@
private StatusBarNotification mStatusBarNotification;
private NotificationChannel mNotificationChannel;
- private ImageView mAutoButton;
- private TextView mImportanceSummary;
- private TextView mImportanceTitle;
- private boolean mAuto;
-
private TextView mNumChannelsView;
private View mChannelDisabledView;
private Switch mChannelEnabledSwitch;
@@ -105,8 +100,10 @@
int appUid = -1;
String appName = pkg;
Drawable pkgicon = null;
+ CharSequence channelNameText = "";
+ ApplicationInfo info = null;
try {
- final ApplicationInfo info = pm.getApplicationInfo(pkg,
+ info = pm.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -115,6 +112,7 @@
appUid = info.uid;
appName = String.valueOf(pm.getApplicationLabel(info));
pkgicon = pm.getApplicationIcon(info);
+
}
} catch (PackageManager.NameNotFoundException e) {
// app is gone, just show package name and generic icon
@@ -135,11 +133,15 @@
R.plurals.notification_num_channels_desc, numChannels), numChannels));
// If this is the placeholder channel, don't use our channel-specific text.
- CharSequence channelNameText;
if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
- channelNameText = channel.getName();
+ if (info != null && channel.getNameResId() != 0) {
+ channelNameText = pm.getText(pkg, channel.getNameResId(), info);
+ }
+ if (channel.getName() != null) {
+ channelNameText = channel.getName();
+ }
}
((TextView) findViewById(R.id.pkgname)).setText(appName);
((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
@@ -152,7 +154,12 @@
iNotificationManager.getNotificationChannelGroupForPackage(
channel.getGroup(), pkg, appUid);
if (notificationChannelGroup != null) {
- groupName = notificationChannelGroup.getName();
+ if (info != null && notificationChannelGroup.getNameResId() != 0) {
+ groupName = pm.getText(pkg, notificationChannelGroup.getNameResId(), info);
+ }
+ if (notificationChannelGroup.getName() != null) {
+ groupName = notificationChannelGroup.getName();
+ }
}
} catch (RemoteException e) {
Log.e(TAG, e.toString());
@@ -171,8 +178,8 @@
boolean nonBlockable = false;
try {
- final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
- nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
+ final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ nonBlockable = Utils.isSystemPackage(getResources(), pm, pkgInfo);
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1cd909ea..8da17fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,7 +41,8 @@
* A notification shelf view that is placed inside the notification scroller. It manages the
* overflow icons that don't fit into the regular list anymore.
*/
-public class NotificationShelf extends ActivatableNotificationView {
+public class NotificationShelf extends ActivatableNotificationView implements
+ View.OnLayoutChangeListener {
public static final boolean SHOW_AMBIENT_ICONS = true;
private static final boolean USE_ANIMATIONS_WHEN_OPENING =
@@ -443,7 +444,8 @@
}
int shelfColor = icon.getStaticDrawableColor();
if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
- int notificationColor = row.getNotificationHeader().getOriginalNotificationColor();
+ int notificationColor
+ = row.getVisibleNotificationHeader().getOriginalNotificationColor();
shelfColor = NotificationUtils.interpolateColors(notificationColor, shelfColor,
iconState.iconAppearAmount);
}
@@ -493,6 +495,10 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ updateRelativeOffset();
+ }
+
+ private void updateRelativeOffset() {
mCollapsedIcons.getLocationOnScreen(mTmp);
mRelativeOffset = mTmp[0];
getLocationOnScreen(mTmp);
@@ -559,6 +565,7 @@
public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
mCollapsedIcons = collapsedIcons;
+ mCollapsedIcons.addOnLayoutChangeListener(this);
}
public void setStatusBarState(int statusBarState) {
@@ -594,6 +601,12 @@
}
}
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ updateRelativeOffset();
+ }
+
private class ShelfState extends ExpandableViewState {
private float openedAmount;
private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index aec9a4b..1101701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -612,8 +612,10 @@
}
public void setIconAppearAmount(float iconAppearAmount) {
- mIconAppearAmount = iconAppearAmount;
- invalidate();
+ if (mIconAppearAmount != iconAppearAmount) {
+ mIconAppearAmount = iconAppearAmount;
+ invalidate();
+ }
}
public float getIconAppearAmount() {
@@ -625,8 +627,10 @@
}
public void setDotAppearAmount(float dotAppearAmount) {
- mDotAppearAmount = dotAppearAmount;
- invalidate();
+ if (mDotAppearAmount != dotAppearAmount) {
+ mDotAppearAmount = dotAppearAmount;
+ invalidate();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InflationException.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InflationException.java
new file mode 100644
index 0000000..b484138
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InflationException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.statusbar.notification;
+
+/**
+ * An exception that something went wrong during the inflation
+ */
+public class InflationException extends Exception {
+ public InflationException(String error) {
+ super(error);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index 78b967a..7a34b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -36,9 +36,9 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
- super.notifyContentUpdated(notification, isLowPriority);
- updateImageTag(notification);
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
+ super.notifyContentUpdated(row);
+ updateImageTag(row.getStatusBarNotification());
}
private void updateImageTag(StatusBarNotification notification) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
index 39db243..9476eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -41,11 +41,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- resolveViews(notification);
- super.notifyContentUpdated(notification, isLowPriority);
+ resolveViews(row.getStatusBarNotification());
+ super.notifyContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index e5f32df..3efa29f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -19,13 +19,8 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.graphics.Color;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.support.v4.graphics.ColorUtils;
import android.view.View;
import com.android.systemui.R;
@@ -40,7 +35,7 @@
private final ViewInvertHelper mInvertHelper;
private final Paint mGreyPaint = new Paint();
- private boolean mShowingLegacyBackground;
+ private boolean mIsLegacy;
private int mLegacyColor;
protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
@@ -55,7 +50,7 @@
return;
}
super.setDark(dark, fade, delay);
- if (!mShowingLegacyBackground && mShouldInvertDark) {
+ if (!mIsLegacy && mShouldInvertDark) {
if (fade) {
mInvertHelper.fade(dark, delay);
} else {
@@ -112,15 +107,14 @@
@Override
public int getCustomBackgroundColor() {
int customBackgroundColor = super.getCustomBackgroundColor();
- if (customBackgroundColor == 0 && mShowingLegacyBackground) {
+ if (customBackgroundColor == 0 && mIsLegacy) {
return mLegacyColor;
}
return customBackgroundColor;
}
- @Override
- public void setShowingLegacyBackground(boolean showing) {
- super.setShowingLegacyBackground(showing);
- mShowingLegacyBackground = showing;
+ public void setLegacy(boolean legacy) {
+ super.setLegacy(legacy);
+ mIsLegacy = legacy;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 8eab2e9..38e4ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -26,7 +27,6 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -71,6 +71,7 @@
private TextView mHeaderText;
private ImageView mWorkProfileImage;
private boolean mIsLowPriority;
+ private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(view, row);
@@ -100,7 +101,7 @@
@Override
protected boolean hasCustomTransformation() {
- return mIsLowPriority;
+ return mIsLowPriority && mTransformLowPriorityTitle;
}
}, TRANSFORMING_VIEW_TITLE);
resolveHeaderViews();
@@ -128,9 +129,10 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
- super.notifyContentUpdated(notification, isLowPriority);
- mIsLowPriority = isLowPriority;
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
+ super.notifyContentUpdated(row);
+ mIsLowPriority = row.isLowPriority();
+ mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
// Reinspect the notification.
@@ -139,11 +141,11 @@
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
- mIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getSmallIcon());
+ Notification notification = row.getStatusBarNotification().getNotification();
+ mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
// The work profile image is always the same lets just set the icon tag for it not to
// animate
- mWorkProfileImage.setTag(ImageTransformState.ICON_TAG,
- notification.getNotification().getSmallIcon());
+ mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
@@ -361,6 +363,12 @@
}
@Override
+ public void setIsChildInGroup(boolean isChildInGroup) {
+ super.setIsChildInGroup(isChildInGroup);
+ mTransformLowPriorityTitle = !isChildInGroup;
+ }
+
+ @Override
public void setVisible(boolean visible) {
super.setVisible(visible);
mTransformationHelper.setVisible(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
new file mode 100644
index 0000000..66703ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2017 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.statusbar.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationContentView;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import java.util.Objects;
+
+/**
+ * A utility that inflates the right kind of contentView based on the state
+ */
+public class NotificationInflater {
+
+ private static final int FLAG_REINFLATE_ALL = ~0;
+ private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
+ private static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
+ private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
+ private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
+ private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
+
+ private final ExpandableNotificationRow mRow;
+ private boolean mIsLowPriority;
+ private boolean mUsesIncreasedHeight;
+ private boolean mUsesIncreasedHeadsUpHeight;
+ private RemoteViews.OnClickHandler mRemoteViewClickHandler;
+ private boolean mIsChildInGroup;
+ private InflationExceptionHandler mInflateExceptionHandler;
+
+ public NotificationInflater(ExpandableNotificationRow row) {
+ mRow = row;
+ }
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
+
+ /**
+ * Set whether the notification is a child in a group
+ *
+ * @return whether the view was re-inflated
+ */
+ public boolean setIsChildInGroup(boolean childInGroup) {
+ if (childInGroup != mIsChildInGroup) {
+ mIsChildInGroup = childInGroup;
+ if (mIsLowPriority) {
+ try {
+ int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
+ inflateNotificationViews(flags);
+ } catch (InflationException e) {
+ mInflateExceptionHandler.handleInflationException(
+ mRow.getStatusBarNotification(), e);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
+ mUsesIncreasedHeight = usesIncreasedHeight;
+ }
+
+ public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
+ mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
+ }
+
+ public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mRemoteViewClickHandler = remoteViewClickHandler;
+ }
+
+ public void inflateNotificationViews() throws InflationException {
+ inflateNotificationViews(FLAG_REINFLATE_ALL);
+ }
+
+ /**
+ * reinflate all views for the specified flags
+ * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
+ * to reinflate all of views.
+ * @throws InflationException
+ */
+ private void inflateNotificationViews(int reInflateFlags)
+ throws InflationException {
+ NotificationData.Entry entry = mRow.getEntry();
+ StatusBarNotification sbn = entry.notification;
+ Context context = mRow.getContext();
+ NotificationContentView privateLayout = mRow.getPrivateLayout();
+ try {
+ final Notification.Builder recoveredBuilder
+ = Notification.Builder.recoverBuilder(context, sbn.getNotification());
+ boolean isLowPriority = mIsLowPriority && !mIsChildInGroup;
+ if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
+ final RemoteViews newContentView = createContentView(recoveredBuilder,
+ isLowPriority, mUsesIncreasedHeadsUpHeight);
+ if (!compareRemoteViews(newContentView,
+ entry.cachedContentView)) {
+ View contentViewLocal = newContentView.apply(
+ sbn.getPackageContext(context),
+ privateLayout,
+ mRemoteViewClickHandler);
+ contentViewLocal.setIsRootNamespace(true);
+ privateLayout.setContractedChild(contentViewLocal);
+ } else {
+ newContentView.reapply(sbn.getPackageContext(context),
+ privateLayout.getContractedChild(),
+ mRemoteViewClickHandler);
+ }
+ entry.cachedContentView = newContentView;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
+ final RemoteViews newBigContentView = createBigContentView(
+ recoveredBuilder, isLowPriority);
+ if (newBigContentView != null) {
+ if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) {
+ View bigContentViewLocal = newBigContentView.apply(
+ sbn.getPackageContext(context),
+ privateLayout,
+ mRemoteViewClickHandler);
+ bigContentViewLocal.setIsRootNamespace(true);
+ privateLayout.setExpandedChild(bigContentViewLocal);
+ } else {
+ newBigContentView.reapply(sbn.getPackageContext(context),
+ privateLayout.getExpandedChild(),
+ mRemoteViewClickHandler);
+ }
+ } else if (entry.cachedBigContentView != null) {
+ privateLayout.setExpandedChild(null);
+ }
+ entry.cachedBigContentView = newBigContentView;
+ mRow.setExpandable(newBigContentView != null);
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
+ final RemoteViews newHeadsUpContentView =
+ recoveredBuilder.createHeadsUpContentView(mUsesIncreasedHeight);
+ if (newHeadsUpContentView != null) {
+ if (!compareRemoteViews(newHeadsUpContentView,
+ entry.cachedHeadsUpContentView)) {
+ View headsUpContentViewLocal = newHeadsUpContentView.apply(
+ sbn.getPackageContext(context),
+ privateLayout,
+ mRemoteViewClickHandler);
+ headsUpContentViewLocal.setIsRootNamespace(true);
+ privateLayout.setHeadsUpChild(headsUpContentViewLocal);
+ } else {
+ newHeadsUpContentView.reapply(sbn.getPackageContext(context),
+ privateLayout.getHeadsUpChild(),
+ mRemoteViewClickHandler);
+ }
+ } else if (entry.cachedHeadsUpContentView != null) {
+ privateLayout.setHeadsUpChild(null);
+ }
+ entry.cachedHeadsUpContentView = newHeadsUpContentView;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
+ NotificationContentView publicLayout = mRow.getPublicLayout();
+ final RemoteViews newPublicNotification
+ = recoveredBuilder.makePublicContentView();
+ if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) {
+ View publicContentView = newPublicNotification.apply(
+ sbn.getPackageContext(context),
+ publicLayout,
+ mRemoteViewClickHandler);
+ publicContentView.setIsRootNamespace(true);
+ publicLayout.setContractedChild(publicContentView);
+ } else {
+ newPublicNotification.reapply(sbn.getPackageContext(context),
+ publicLayout.getContractedChild(),
+ mRemoteViewClickHandler);
+ }
+ entry.cachedPublicContentView = newPublicNotification;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
+ final RemoteViews newAmbientNotification
+ = recoveredBuilder.makeAmbientNotification();
+ if (!compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) {
+ View ambientContentView = newAmbientNotification.apply(
+ sbn.getPackageContext(context),
+ privateLayout,
+ mRemoteViewClickHandler);
+ ambientContentView.setIsRootNamespace(true);
+ privateLayout.setAmbientChild(ambientContentView);
+ } else {
+ newAmbientNotification.reapply(sbn.getPackageContext(context),
+ privateLayout.getAmbientChild(),
+ mRemoteViewClickHandler);
+ }
+ entry.cachedAmbientContentView = newAmbientNotification;
+ }
+
+ } catch (RuntimeException e) {
+ final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
+ Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
+ throw new InflationException("Couldn't inflate contentViews");
+ }
+ }
+
+ private RemoteViews createBigContentView(Notification.Builder builder,
+ boolean isLowPriority) {
+ RemoteViews bigContentView = builder.createBigContentView();
+ if (bigContentView != null) {
+ return bigContentView;
+ }
+ if (isLowPriority) {
+ RemoteViews contentView = builder.createContentView();
+ Notification.Builder.makeHeaderExpanded(contentView);
+ return contentView;
+ }
+ return null;
+ }
+
+ private RemoteViews createContentView(Notification.Builder builder,
+ boolean isLowPriority, boolean useLarge) {
+ if (isLowPriority) {
+ return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
+ }
+ return builder.createContentView(useLarge);
+ }
+
+ // Returns true if the RemoteViews are the same.
+ private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
+ return (a == null && b == null) ||
+ (a != null && b != null
+ && b.getPackage() != null
+ && a.getPackage() != null
+ && a.getPackage().equals(b.getPackage())
+ && a.getLayoutId() == b.getLayoutId());
+ }
+
+ public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) {
+ mInflateExceptionHandler = inflateExceptionHandler;
+ }
+
+ public interface InflationExceptionHandler {
+ void handleInflationException(StatusBarNotification notification, InflationException e);
+ }
+ public void onDensityOrFontScaleChanged() {
+ NotificationData.Entry entry = mRow.getEntry();
+ entry.cachedAmbientContentView = null;
+ entry.cachedBigContentView = null;
+ entry.cachedContentView = null;
+ entry.cachedHeadsUpContentView = null;
+ entry.cachedPublicContentView = null;
+ try {
+ inflateNotificationViews();
+ } catch (InflationException e) {
+ mInflateExceptionHandler.handleInflationException(
+ mRow.getStatusBarNotification(), e);
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 04ee6aa..ef5a25c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -35,16 +35,16 @@
View mActions;
- private void resolveViews(StatusBarNotification notification) {
+ private void resolveViews() {
mActions = mView.findViewById(com.android.internal.R.id.media_actions);
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- resolveViews(notification);
- super.notifyContentUpdated(notification, isLowPriority);
+ resolveViews();
+ super.notifyContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index defeab2..9631556 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -21,7 +21,6 @@
import com.android.systemui.statusbar.TransformableView;
import android.content.Context;
-import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
@@ -71,11 +70,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews();
- super.notifyContentUpdated(notification, isLowPriority);
+ super.notifyContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index e9956ff..846d03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -133,11 +133,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- resolveTemplateViews(notification);
- super.notifyContentUpdated(notification, isLowPriority);
+ resolveTemplateViews(row.getStatusBarNotification());
+ super.notifyContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5f5e1e4..c85e8d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -48,6 +48,7 @@
private int mBackgroundColor = 0;
protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
+ private boolean mForcedInvisible;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -87,10 +88,9 @@
/**
* Notifies this wrapper that the content of the view might have changed.
- * @param notification the notification this is wrapped around
- * @param isLowPriority is this notification low priority
+ * @param row the row this wrapper is attached to
*/
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ public void notifyContentUpdated(ExpandableNotificationRow row) {
mDarkInitialized = false;
Drawable background = mView.getBackground();
if (shouldClearBackgroundOnReapply()) {
@@ -185,7 +185,7 @@
return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
}
- public void setShowingLegacyBackground(boolean showing) {
+ public void setLegacy(boolean legacy) {
}
public void setContentHeight(int contentHeight, int minHeightHint) {
@@ -193,4 +193,7 @@
public void setRemoteInputVisible(boolean visible) {
}
+
+ public void setIsChildInGroup(boolean isChildInGroup) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 1f56c56..6cd3eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -153,7 +153,7 @@
}
private boolean shouldHideNotificationIcons() {
- return !mStatusBar.isClosed() && mStatusBarComponent.shouldHideNotificationIcons();
+ return !mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded();
}
public void hideSystemIconArea(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 707997d..4581204 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -79,7 +79,9 @@
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
View child = mNotificationIcons.getChildAt(i);
child.setLayoutParams(params);
- child = mShelfIcons.getChildAt(i);
+ }
+ for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
+ View child = mShelfIcons.getChildAt(i);
child.setLayoutParams(params);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index dc5f98c..3706dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -227,6 +227,7 @@
ViewState iconState = mIconStates.get(view);
iconState.initFrom(view);
iconState.alpha = 1.0f;
+ iconState.hidden = false;
}
}
@@ -524,11 +525,5 @@
iconColor = ((StatusBarIconView) view).getStaticDrawableColor();
}
}
-
- protected void onYTranslationAnimationFinished(View view) {
- if (hidden) {
- view.setVisibility(INVISIBLE);
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 468fb57..e6d3168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -47,7 +46,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.DejankUtils;
-import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -179,7 +177,7 @@
private boolean mKeyguardStatusViewAnimating;
private ValueAnimator mQsSizeChangeAnimator;
- private boolean mShadeEmpty;
+ private boolean mShowEmptyShadeView;
private boolean mQsScrimEnabled = true;
private boolean mLastAnnouncementWasQuickSettings;
@@ -211,11 +209,12 @@
}
};
private NotificationGroupManager mGroupManager;
- private boolean mOpening;
+ private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private boolean mIsFullWidth;
private boolean mDark;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private boolean mNoVisibleNotifications = true;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -1518,7 +1517,7 @@
// it in expanded QS state as well so we don't run into troubles when fading the view in/out
// and expanding/collapsing the whole panel from/to quick settings.
if (mNotificationStackScroller.getNotGoneChildCount() == 0
- && mShadeEmpty) {
+ && mShowEmptyShadeView) {
notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
}
int maxQsHeight = mQsMaxExpansionHeight;
@@ -2118,15 +2117,15 @@
return mDozing;
}
- public void setShadeEmpty(boolean shadeEmpty) {
- mShadeEmpty = shadeEmpty;
+ public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+ mShowEmptyShadeView = emptyShadeViewVisible;
updateEmptyShadeView();
}
private void updateEmptyShadeView() {
// Hide "No notifications" in QS.
- mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
+ mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
}
public void setQsScrimEnabled(boolean qsScrimEnabled) {
@@ -2306,7 +2305,7 @@
}
mNotificationStackScroller.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
- setOpening(isFullWidth() && expandedHeight < getOpeningHeight());
+ updateStatusBarIcons();
}
/**
@@ -2317,13 +2316,21 @@
return mIsFullWidth;
}
- private void setOpening(boolean opening) {
- if (opening != mOpening) {
- mOpening = opening;
+ private void updateStatusBarIcons() {
+ boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+ if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
+ showIconsWhenExpanded = false;
+ }
+ if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+ mShowIconsWhenExpanded = showIconsWhenExpanded;
mStatusBar.recomputeDisableFlags(false);
}
}
+ private boolean isOnKeyguard() {
+ return mStatusBar.getBarState() == StatusBarState.KEYGUARD;
+ }
+
public void setPanelScrimMinFraction(float minFraction) {
mBar.panelScrimMinFractionChanged(minFraction);
}
@@ -2426,12 +2433,8 @@
mGroupManager = groupManager;
}
- public boolean shouldHideNotificationIcons() {
- return !isFullWidth() || (!mOpening && !isFullyCollapsed());
- }
-
- public boolean shouldAnimateIconHiding() {
- return !isFullWidth();
+ public boolean hideStatusBarIconsWhenExpanded() {
+ return !isFullWidth() || !mShowIconsWhenExpanded;
}
private final FragmentListener mFragmentListener = new FragmentListener() {
@@ -2473,4 +2476,8 @@
mKeyguardStatusView.setDark(dark);
positionClockAndNotifications();
}
+
+ public void setNoVisibleNotifications(boolean noNotifications) {
+ mNoVisibleNotifications = noNotifications;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9e93ed3..bb6c8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.AlarmManager;
@@ -25,8 +24,6 @@
import android.app.AppGlobals;
import android.app.Notification;
import android.app.Notification.Action;
-import android.app.Notification.BigTextStyle;
-import android.app.Notification.Style;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
@@ -37,7 +34,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
@@ -51,7 +47,6 @@
import android.provider.Settings.Global;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -61,11 +56,9 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.R.string;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.CommandQueue;
@@ -87,9 +80,6 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.NotificationChannels;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* This class contains all of the policy about which icons are installed in the status
* bar at boot time. It goes through the normal API for icons, even though it probably
@@ -137,8 +127,6 @@
private boolean mVolumeVisible;
private boolean mCurrentUserSetup;
- private int mZen;
-
private boolean mManagedProfileFocused = false;
private boolean mManagedProfileIconVisible = false;
private boolean mManagedProfileInQuietMode = false;
@@ -275,14 +263,14 @@
@Override
public void onZenChanged(int zen) {
- mZen = zen;
updateVolumeZen();
}
private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
- final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ int zen = mZenController.getZen();
+ final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim
: R.drawable.stat_sys_alarm, null);
mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm);
@@ -323,17 +311,18 @@
boolean volumeVisible = false;
int volumeIconId = 0;
String volumeDescription = null;
+ int zen = mZenController.getZen();
if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
- zenVisible = mZen != Global.ZEN_MODE_OFF;
- zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS
+ zenVisible = zen != Global.ZEN_MODE_OFF;
+ zenIconId = zen == Global.ZEN_MODE_NO_INTERRUPTIONS
? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd;
zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
- } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_none;
zenDescription = mContext.getString(R.string.interruption_level_none);
- } else if (mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_important;
zenDescription = mContext.getString(R.string.interruption_level_priority);
@@ -344,7 +333,7 @@
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_silent;
volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
- } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS &&
+ } else if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS &&
audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_vibrate;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index a1022c4..de00237 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -37,7 +37,7 @@
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.tiles.AirplaneModeTile;
-import com.android.systemui.qs.tiles.BatteryTile;
+import com.android.systemui.qs.tiles.BatterySaverTile;
import com.android.systemui.qs.tiles.BluetoothTile;
import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
@@ -273,7 +273,7 @@
else if (tileSpec.equals("cast")) return new CastTile(this);
else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
else if (tileSpec.equals("user")) return new UserTile(this);
- else if (tileSpec.equals("battery")) return new BatteryTile(this);
+ else if (tileSpec.equals("battery")) return new BatterySaverTile(this);
else if (tileSpec.equals("saver")) return new DataSaverTile(this);
else if (tileSpec.equals("night")) return new NightDisplayTile(this);
else if (tileSpec.equals("nfc")) return new NfcTile(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 648e00c..c90d493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -23,6 +23,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+import static com.android.systemui.statusbar.notification.NotificationInflater.InflationExceptionHandler;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -160,7 +161,6 @@
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationGuts;
@@ -171,6 +171,7 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
@@ -298,7 +299,7 @@
"com.android.systemui.statusbar.banner_action_setup";
private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
= "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
- static final String TAG = "StatusBar";
+ public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
@@ -715,7 +716,8 @@
private LogMaker mStatusBarStateLog;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private NotificationIconAreaController mNotificationIconAreaController;
- private ConfigurationListener mDensityChangeListener;
+ private ConfigurationListener mConfigurationListener;
+ private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
@@ -942,13 +944,18 @@
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
- mDensityChangeListener = new ConfigurationListener() {
+ mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ StatusBar.this.onConfigurationChanged(newConfig);
+ }
+
@Override
public void onDensityOrFontScaleChanged() {
StatusBar.this.onDensityOrFontScaleChanged();
}
};
- Dependency.get(ConfigurationController.class).addCallback(mDensityChangeListener);
+ Dependency.get(ConfigurationController.class).addCallback(mConfigurationListener);
}
protected void createIconController() {
@@ -1275,12 +1282,11 @@
Entry entry = activeNotifications.get(i);
boolean exposedGuts = mNotificationGutsExposed != null
&& entry.row.getGuts() == mNotificationGutsExposed;
- entry.row.reInflateViews();
+ entry.row.onDensityOrFontScaleChanged();
if (exposedGuts) {
mNotificationGutsExposed = entry.row.getGuts();
bindGuts(entry.row, mGutsMenuItem);
}
- inflateViews(entry, mStackScroller);
}
// end old BaseStatusBar.onDensityOrFontScaleChanged().
mScrimController.onDensityOrFontScaleChanged();
@@ -1549,14 +1555,11 @@
}
public void addNotification(StatusBarNotification notification, RankingMap ranking,
- Entry oldEntry) {
+ Entry oldEntry) throws InflationException {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
- if (shadeEntry == null) {
- return;
- }
boolean isHeadsUped = shouldPeek(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
@@ -1598,6 +1601,10 @@
setAreThereNotifications();
}
+ public void handleInflationException(StatusBarNotification notification, InflationException e) {
+ handleNotificationError(notification, e.getMessage());
+ }
+
private boolean shouldSuppressFullScreenIntent(String key) {
if (isDeviceInVrMode()) {
return true;
@@ -1662,10 +1669,17 @@
sbn.getOpPkg(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
-
- updateNotification(newSbn, null);
- mKeysKeptForRemoteInput.add(entry.key);
- return;
+ boolean updated = false;
+ try {
+ updateNotification(newSbn, null);
+ updated = true;
+ } catch (InflationException e) {
+ deferRemoval = false;
+ }
+ if (updated) {
+ mKeysKeptForRemoteInput.add(entry.key);
+ return;
+ }
}
if (deferRemoval) {
mLatestRankingMap = ranking;
@@ -1807,15 +1821,6 @@
updatePublicContentView(ent, ent.notification);
}
ent.row.setSensitive(sensitive, deviceSensitive);
- if (ent.autoRedacted && ent.legacy) {
- // TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
- // for legacy auto redacted notifications.
- if (showingPublic) {
- ent.row.setShowingLegacyBackground(false);
- } else {
- ent.row.setShowingLegacyBackground(true);
- }
- }
if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
ent.row.getStatusBarNotification());
@@ -2033,10 +2038,10 @@
}
private void updateEmptyShadeView() {
- boolean showEmptyShade =
+ boolean showEmptyShadeView =
mState != StatusBarState.KEYGUARD &&
mNotificationData.getActiveNotifications().size() == 0;
- mNotificationPanel.setShadeEmpty(showEmptyShade);
+ mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
}
private void updateSpeedBumpIndex() {
@@ -2719,8 +2724,8 @@
return mLaunchTransitionFadingAway;
}
- public boolean shouldHideNotificationIcons() {
- return mNotificationPanel.shouldHideNotificationIcons();
+ public boolean hideStatusBarIconsWhenExpanded() {
+ return mNotificationPanel.hideStatusBarIconsWhenExpanded();
}
/**
@@ -2962,7 +2967,7 @@
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
showBouncerIfKeyguard();
- recomputeDisableFlags(shouldAnimatIconHiding() /* animate */);
+ recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
@@ -2971,10 +2976,6 @@
}
}
- private boolean shouldAnimatIconHiding() {
- return mNotificationPanel.shouldAnimateIconHiding();
- }
-
public boolean interceptTouchEvent(MotionEvent event) {
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -3909,7 +3910,7 @@
}
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
mDeviceProvisionedController.removeCallback(mUserSetupObserver);
- Dependency.get(ConfigurationController.class).removeCallback(mDensityChangeListener);
+ Dependency.get(ConfigurationController.class).removeCallback(mConfigurationListener);
}
private boolean mDemoModeAllowed;
@@ -5512,7 +5513,11 @@
@Override
public void run() {
for (StatusBarNotification sbn : notifications) {
- addNotification(sbn, currentRanking, null /* oldEntry */);
+ try {
+ addNotification(sbn, currentRanking, null /* oldEntry */);
+ } catch (InflationException e) {
+ handleInflationException(sbn, e);
+ }
}
}
});
@@ -5548,10 +5553,14 @@
}
return;
}
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap, null /* oldEntry */);
+ try {
+ if (isUpdate) {
+ updateNotification(sbn, rankingMap);
+ } else {
+ addNotification(sbn, rankingMap, null /* oldEntry */);
+ }
+ } catch (InflationException e) {
+ handleInflationException(sbn, e);
}
}
});
@@ -5681,26 +5690,6 @@
return mGroupManager;
}
- protected void bindDismissRunnable(final ExpandableNotificationRow row) {
- row.setOnDismissRunnable(() -> performRemoveNotification(row.getStatusBarNotification()));
- }
-
- protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
- NotificationData.Entry entry) {
-
- if (entry.getContentView().getId()
- != com.android.internal.R.id.status_bar_latest_event_content) {
- // Using custom RemoteViews
- if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
- && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
- entry.row.setShowingLegacyBackground(true);
- entry.legacy = true;
- }
- }
-
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- }
-
public boolean isMediaNotification(NotificationData.Entry entry) {
// TODO: confirm that there's a valid media key
return entry.getExpandedContentView() != null &&
@@ -6082,55 +6071,16 @@
}
}
- protected boolean inflateViews(Entry entry, ViewGroup parent) {
+ protected void inflateViews(Entry entry, ViewGroup parent) throws
+ InflationException {
PackageManager pmUser = getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
- boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
- boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
- mNotificationData.getImportance(sbn.getKey()));
- boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
- try {
- entry.cacheContentViews(mContext, null, isLowPriority, useIncreasedCollapsedHeight,
- useIncreasedHeadsUp);
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to get notification remote views", e);
- return false;
- }
-
- final RemoteViews contentView = entry.cachedContentView;
- final RemoteViews bigContentView = entry.cachedBigContentView;
- final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
- final RemoteViews publicContentView = entry.cachedPublicContentView;
- final RemoteViews ambientContentView = entry.cachedAmbientContentView;
-
- if (contentView == null) {
- Log.v(TAG, "no contentView for: " + sbn.getNotification());
- return false;
- }
-
- if (DEBUG) {
- Log.v(TAG, "publicContentView: " + publicContentView);
- }
-
ExpandableNotificationRow row;
-
- // Stash away previous user expansion state so we can restore it at
- // the end.
- boolean hasUserChangedExpansion = false;
- boolean userExpanded = false;
- boolean userLocked = false;
-
if (entry.row != null) {
row = entry.row;
- hasUserChangedExpansion = row.hasUserChangedExpansion();
- userExpanded = row.isUserExpanded();
- userLocked = row.isUserLocked();
entry.reset();
- if (hasUserChangedExpansion) {
- row.setUserExpanded(userExpanded);
- }
} else {
// create the row view
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
@@ -6142,6 +6092,8 @@
row.setHeadsUpManager(mHeadsUpManager);
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
+ row.setRemoteViewClickHandler(mOnClickHandler);
+ row.setInflateExceptionHandler(mInflationExceptionHandler);
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
@@ -6161,86 +6113,19 @@
// Do nothing
}
row.setAppName(appname);
+ row.setOnDismissRunnable(() ->
+ performRemoveNotification(row.getStatusBarNotification()));
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
}
- bindDismissRunnable(row);
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
row.setIsLowPriority(isLowPriority);
-
- // NB: the large icon is now handled entirely by the template
-
// bind the click event to the content area
- NotificationContentView contentContainer = row.getPrivateLayout();
- NotificationContentView contentContainerPublic = row.getPublicLayout();
-
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
-
mNotificationClicker.register(row, sbn);
- // set up the adaptive layout
- View contentViewLocal = null;
- View bigContentViewLocal = null;
- View headsUpContentViewLocal = null;
- View publicViewLocal = null;
- View ambientViewLocal = null;
- try {
- contentViewLocal = contentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- }
- if (headsUpContentView != null) {
- headsUpContentViewLocal = headsUpContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer,
- mOnClickHandler);
- }
- if (publicContentView != null) {
- publicViewLocal = publicContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainerPublic, mOnClickHandler);
- }
- if (ambientContentView != null) {
- ambientViewLocal = ambientContentView.apply(
- sbn.getPackageContext(mContext),
- contentContainer, mOnClickHandler);
- }
-
- if (contentViewLocal != null) {
- contentViewLocal.setIsRootNamespace(true);
- contentContainer.setContractedChild(contentViewLocal);
- }
- if (bigContentViewLocal != null) {
- bigContentViewLocal.setIsRootNamespace(true);
- contentContainer.setExpandedChild(bigContentViewLocal);
- }
- if (headsUpContentViewLocal != null) {
- headsUpContentViewLocal.setIsRootNamespace(true);
- contentContainer.setHeadsUpChild(headsUpContentViewLocal);
- }
- if (publicViewLocal != null) {
- publicViewLocal.setIsRootNamespace(true);
- contentContainerPublic.setContractedChild(publicViewLocal);
- }
-
- if (ambientViewLocal != null) {
- ambientViewLocal.setIsRootNamespace(true);
- contentContainer.setAmbientChild(ambientViewLocal);
- }
- }
- catch (RuntimeException e) {
- final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
- Log.e(TAG, "couldn't inflate view for notification " + ident, e);
- return false;
- }
-
// Extract target SDK version.
try {
ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
@@ -6248,25 +6133,20 @@
} catch (NameNotFoundException ex) {
Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
}
+ row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+ && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
entry.row = row;
entry.row.setOnActivatedListener(this);
- entry.row.setExpandable(bigContentViewLocal != null);
- applyColorsAndBackgrounds(sbn, entry);
-
- // Restore previous flags.
- if (hasUserChangedExpansion) {
- // Note: setUserExpanded() conveniently ignores calls with
- // userExpanded=true if !isExpandable().
- row.setUserExpanded(userExpanded);
- }
- row.setUserLocked(userLocked);
+ boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+ mNotificationData.getImportance(sbn.getKey()));
+ boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.onNotificationUpdated(entry);
- return true;
+ row.updateNotification(entry);
}
/**
@@ -6604,23 +6484,17 @@
return entry.notification;
}
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
+ protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ throws InflationException {
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
- try {
- entry.createIcons(mContext, sbn);
- } catch (NotificationData.IconException exception) {
- handleNotificationError(sbn, exception.getMessage());
- }
+ entry.createIcons(mContext, sbn);
// Construct the expanded view.
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
- return null;
- }
+ inflateViews(entry, mStackScroller);
return entry;
}
@@ -6696,6 +6570,7 @@
}
}
}
+ mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
@@ -6741,7 +6616,8 @@
}
}
- public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
+ public void updateNotification(StatusBarNotification notification, RankingMap ranking)
+ throws InflationException {
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
@@ -6756,56 +6632,16 @@
Notification n = notification.getNotification();
mNotificationData.updateRanking(ranking);
- boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(notification,
- mNotificationData.getImportance(notification.getKey()));
- entry.row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
- boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
- entry.row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- boolean applyInPlace;
- try {
- applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
- mNotificationData.isAmbient(key), useIncreasedCollapsedHeight,
- useIncreasedHeadsUp);
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to get notification remote views", e);
- applyInPlace = false;
- }
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
- if (DEBUG) {
- Log.d(TAG, "applyInPlace=" + applyInPlace
- + " shouldPeek=" + shouldPeek
- + " alertAgain=" + alertAgain);
- }
-
final StatusBarNotification oldNotification = entry.notification;
entry.notification = notification;
mGroupManager.onEntryUpdated(entry, oldNotification);
- boolean updateSuccessful = false;
- try {
- if (applyInPlace) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- try {
- entry.updateIcons(mContext, n);
- updateNotificationViews(entry, notification);
- updateSuccessful = true;
- } catch (RuntimeException e) {
- // It failed to apply cleanly.
- Log.w(TAG, "Couldn't reapply views for package " +
- notification.getPackageName(), e);
- }
- }
- if (!updateSuccessful) {
- entry.updateIcons(mContext, n);
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(notification, "Couldn't update remote views for: "
- + notification);
- }
- }
- } catch (NotificationData.IconException e) {
- handleNotificationError(notification, e.getMessage());
- }
+ entry.updateIcons(mContext, n);
+ inflateViews(entry, mStackScroller);
+
+ boolean shouldPeek = shouldPeek(entry, notification);
+ boolean alertAgain = alertAgain(entry, n);
+
updateHeadsUp(key, entry, shouldPeek, alertAgain);
updateNotifications();
@@ -6820,44 +6656,9 @@
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
}
-
setAreThereNotifications();
}
- private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
- final RemoteViews contentView = entry.cachedContentView;
- final RemoteViews bigContentView = entry.cachedBigContentView;
- final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
- final RemoteViews publicContentView = entry.cachedPublicContentView;
- final RemoteViews ambientContentView = entry.cachedAmbientContentView;
-
- // Reapply the RemoteViews
- contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
- if (bigContentView != null && entry.getExpandedContentView() != null) {
- bigContentView.reapply(sbn.getPackageContext(mContext),
- entry.getExpandedContentView(),
- mOnClickHandler);
- }
- View headsUpChild = entry.getHeadsUpContentView();
- if (headsUpContentView != null && headsUpChild != null) {
- headsUpContentView.reapply(sbn.getPackageContext(mContext),
- headsUpChild, mOnClickHandler);
- }
- if (publicContentView != null && entry.getPublicContentView() != null) {
- publicContentView.reapply(sbn.getPackageContext(mContext),
- entry.getPublicContentView(), mOnClickHandler);
- }
- if (ambientContentView != null && entry.getAmbientContentView() != null) {
- ambientContentView.reapply(sbn.getPackageContext(mContext),
- entry.getAmbientContentView(), mOnClickHandler);
- }
- // update the contentIntent
- mNotificationClicker.register(entry.row, sbn);
-
- entry.row.onNotificationUpdated(entry);
- entry.row.resetHeight();
- }
-
protected void updatePublicContentView(Entry entry,
StatusBarNotification sbn) {
final RemoteViews publicContentView = entry.cachedPublicContentView;
@@ -6871,10 +6672,7 @@
TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
if (titleView != null
&& !titleView.getText().toString().equals(notificationHiddenText)) {
- publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
- publicContentView.reapply(sbn.getPackageContext(mContext),
- inflatedView, mOnClickHandler);
- entry.row.onNotificationUpdated(entry);
+ titleView.setText(notificationHiddenText);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index 821e635..a87b50a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -116,19 +116,20 @@
}
private void notifyKeyguardChanged() {
- mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+ // Copy the list to allow removal during callback.
+ new ArrayList<Callback>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
}
public void notifyKeyguardFadingAway(long delay, long fadeoutDuration) {
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
- mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+ notifyKeyguardChanged();
}
public void notifyKeyguardDoneFading() {
mKeyguardFadingAway = false;
- mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+ notifyKeyguardChanged();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 51d931e..bc3eec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -39,6 +39,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
@@ -78,6 +79,7 @@
private static final int EMERGENCY_FIRST_CONTROLLER = 100;
private static final int EMERGENCY_VOICE_CONTROLLER = 200;
private static final int EMERGENCY_NO_SUB = 300;
+ private static final int EMERGENCY_ASSUMED_VOICE_CONTROLLER = 400;
private final Context mContext;
private final TelephonyManager mPhone;
@@ -99,8 +101,7 @@
final EthernetSignalController mEthernetSignalController;
@VisibleForTesting
- final Map<Integer, MobileSignalController> mMobileSignalControllers =
- new HashMap<Integer, MobileSignalController>();
+ final SparseArray<MobileSignalController> mMobileSignalControllers = new SparseArray<>();
// When no SIMs are around at setup, and one is added later, it seems to default to the first
// SIM for most actions. This may be null if there aren't any SIMs around.
private MobileSignalController mDefaultSignalController;
@@ -232,7 +233,8 @@
}
private void registerListeners() {
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.registerListener();
}
if (mSubscriptionListener == null) {
@@ -261,7 +263,8 @@
private void unregisterListeners() {
mListening = false;
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.unregisterListener();
}
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
@@ -305,7 +308,7 @@
if (DEBUG) Log.e(TAG, "No data sim selected");
return mDefaultSignalController;
}
- if (mMobileSignalControllers.containsKey(dataSubId)) {
+ if (mMobileSignalControllers.indexOfKey(dataSubId) >= 0) {
return mMobileSignalControllers.get(dataSubId);
}
if (DEBUG) Log.e(TAG, "Cannot find controller for data sub: " + dataSubId);
@@ -326,8 +329,8 @@
}
int voiceSubId = mSubDefaults.getDefaultVoiceSubId();
if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
- for (MobileSignalController mobileSignalController :
- mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
if (!mobileSignalController.getState().isEmergency) {
mEmergencySource = EMERGENCY_FIRST_CONTROLLER
+ mobileSignalController.mSubscriptionInfo.getSubscriptionId();
@@ -336,11 +339,20 @@
}
}
}
- if (mMobileSignalControllers.containsKey(voiceSubId)) {
+ if (mMobileSignalControllers.indexOfKey(voiceSubId) >= 0) {
mEmergencySource = EMERGENCY_VOICE_CONTROLLER + voiceSubId;
if (DEBUG) Log.d(TAG, "Getting emergency from " + voiceSubId);
return mMobileSignalControllers.get(voiceSubId).getState().isEmergency;
}
+ // If we have the wrong subId but there is only one sim anyway, assume it should be the
+ // default.
+ if (mMobileSignalControllers.size() == 1) {
+ mEmergencySource = EMERGENCY_ASSUMED_VOICE_CONTROLLER
+ + mMobileSignalControllers.keyAt(0);
+ if (DEBUG) Log.d(TAG, "Getting assumed emergency from "
+ + mMobileSignalControllers.keyAt(0));
+ return mMobileSignalControllers.valueAt(0).getState().isEmergency;
+ }
if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
mEmergencySource = EMERGENCY_NO_SUB + voiceSubId;
// Something is wrong, better assume we can't make calls...
@@ -363,7 +375,8 @@
cb.setNoSims(mHasNoSims);
mWifiSignalController.notifyListeners(cb);
mEthernetSignalController.notifyListeners(cb);
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
}
mCallbackHandler.setListening(cb, true);
@@ -416,7 +429,8 @@
} else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
// Notify every MobileSignalController so they can know whether they are the
// data sim or not.
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.handleBroadcast(intent);
}
} else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
@@ -433,7 +447,7 @@
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
- if (mMobileSignalControllers.containsKey(subId)) {
+ if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
mMobileSignalControllers.get(subId).handleBroadcast(intent);
} else {
// Can't find this subscription... We must be out of date.
@@ -458,8 +472,9 @@
@VisibleForTesting
void handleConfigurationChanged() {
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
- mobileSignalController.setConfiguration(mConfig);
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+ controller.setConfiguration(mConfig);
}
refreshLocale();
}
@@ -511,15 +526,20 @@
});
mCurrentSubscriptions = subscriptions;
- HashMap<Integer, MobileSignalController> cachedControllers =
- new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
+ SparseArray<MobileSignalController> cachedControllers =
+ new SparseArray<MobileSignalController>();
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ cachedControllers.put(mMobileSignalControllers.keyAt(i),
+ mMobileSignalControllers.valueAt(i));
+ }
mMobileSignalControllers.clear();
final int num = subscriptions.size();
for (int i = 0; i < num; i++) {
int subId = subscriptions.get(i).getSubscriptionId();
// If we have a copy of this controller already reuse it, otherwise make a new one.
- if (cachedControllers.containsKey(subId)) {
- mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
+ if (cachedControllers.indexOfKey(subId) >= 0) {
+ mMobileSignalControllers.put(subId, cachedControllers.get(subId));
+ cachedControllers.remove(subId);
} else {
MobileSignalController controller = new MobileSignalController(mContext, mConfig,
mHasMobileDataFeature, mPhone, mCallbackHandler,
@@ -535,7 +555,8 @@
}
}
if (mListening) {
- for (Integer key : cachedControllers.keySet()) {
+ for (int i = 0; i < cachedControllers.size(); i++) {
+ int key = cachedControllers.keyAt(i);
if (cachedControllers.get(key) == mDefaultSignalController) {
mDefaultSignalController = null;
}
@@ -557,7 +578,8 @@
private void handleSetUserSetupComplete(boolean userSetup) {
mUserSetup = userSetup;
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.setUserSetupComplete(mUserSetup);
}
}
@@ -568,7 +590,7 @@
return false;
}
for (SubscriptionInfo info : allSubscriptions) {
- if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) {
+ if (mMobileSignalControllers.indexOfKey(info.getSubscriptionId()) < 0) {
return false;
}
}
@@ -580,7 +602,8 @@
Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
if (airplaneMode != mAirplaneMode || force) {
mAirplaneMode = airplaneMode;
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.setAirplaneMode(mAirplaneMode);
}
notifyListeners();
@@ -601,7 +624,8 @@
*/
private void notifyAllListeners() {
notifyListeners();
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners();
}
mWifiSignalController.notifyListeners();
@@ -650,7 +674,8 @@
*/
private void pushConnectivityToSignals() {
// We want to update all the icons, all at once, for any condition change
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
}
mWifiSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
@@ -682,7 +707,8 @@
pw.print(" mEmergencySource=");
pw.println(emergencyToString(mEmergencySource));
- for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.dump(pw);
}
mWifiSignalController.dump(pw);
@@ -694,6 +720,9 @@
private static final String emergencyToString(int emergencySource) {
if (emergencySource > EMERGENCY_NO_SUB) {
+ return "ASSUMED_VOICE_CONTROLLER(" + (emergencySource - EMERGENCY_VOICE_CONTROLLER)
+ + ")";
+ } else if (emergencySource > EMERGENCY_NO_SUB) {
return "NO_SUB(" + (emergencySource - EMERGENCY_NO_SUB) + ")";
} else if (emergencySource > EMERGENCY_VOICE_CONTROLLER) {
return "VOICE_CONTROLLER(" + (emergencySource - EMERGENCY_VOICE_CONTROLLER) + ")";
@@ -724,7 +753,8 @@
// Update what MobileSignalControllers, because they may change
// to set the number of sim slots.
updateMobileControllers();
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.resetLastState();
}
mWifiSignalController.resetLastState();
@@ -747,7 +777,8 @@
connected.set(mWifiSignalController.mTransportType);
}
mWifiSignalController.updateConnectivity(connected, connected);
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
if (mDemoInetCondition) {
connected.set(controller.mTransportType);
}
@@ -820,8 +851,7 @@
mCallbackHandler.setSubs(subs);
}
// Hack to index linearly for easy use.
- MobileSignalController controller = mMobileSignalControllers
- .values().toArray(new MobileSignalController[0])[slot];
+ MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
controller.getState().dataSim = datatype != null;
controller.getState().isDefault = datatype != null;
controller.getState().dataConnected = datatype != null;
@@ -875,7 +905,8 @@
String carrierNetworkChange = args.getString("carriernetworkchange");
if (carrierNetworkChange != null) {
boolean show = carrierNetworkChange.equals("show");
- for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.setCarrierNetworkChangeMode(show);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index e6a3add..83cbd72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -52,6 +52,14 @@
private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
+ private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(200);
private final List<View> mDividers = new ArrayList<>();
private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
@@ -64,7 +72,7 @@
private float mCollapsedBottompadding;
private ViewInvertHelper mOverflowInvertHelper;
private boolean mChildrenExpanded;
- private ExpandableNotificationRow mNotificationParent;
+ private ExpandableNotificationRow mContainingNotification;
private TextView mOverflowNumber;
private ViewState mGroupOverFlowState;
private int mRealHeight;
@@ -75,10 +83,15 @@
private NotificationHeaderView mNotificationHeader;
private NotificationViewWrapper mNotificationHeaderWrapper;
+ private NotificationHeaderView mNotificationHeaderLowPriority;
+ private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
private NotificationHeaderUtil mHeaderUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
private boolean mIsLowPriority;
+ private boolean mHeaderVisible = true;
+ private OnClickListener mHeaderClickListener;
+ private boolean mShowingNormalHeader;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -135,6 +148,11 @@
mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
mNotificationHeader.getMeasuredHeight());
}
+ if (mNotificationHeaderLowPriority != null) {
+ mNotificationHeaderLowPriority.layout(0, 0,
+ mNotificationHeaderLowPriority.getMeasuredWidth(),
+ mNotificationHeaderLowPriority.getMeasuredHeight());
+ }
}
@Override
@@ -178,10 +196,14 @@
height = Math.min(height, size);
}
+ int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
if (mNotificationHeader != null) {
- int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
}
+ if (mNotificationHeaderLowPriority != null) {
+ headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
+ mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
+ }
setMeasuredDimension(width, height);
}
@@ -247,30 +269,66 @@
return mChildren.size();
}
- public void recreateNotificationHeader(OnClickListener listener,
- StatusBarNotification notification) {
+ public void recreateNotificationHeader(OnClickListener listener) {
+ mHeaderClickListener = listener;
+ StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
- mNotificationParent.getStatusBarNotification().getNotification());
- final RemoteViews header = mIsLowPriority
- ? builder.makeLowPriorityContentView(true /* useRegularSubtext */)
- : builder.makeNotificationHeader();
+ notification.getNotification());
+ RemoteViews header = builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
com.android.internal.R.id.expand_button);
expandButton.setVisibility(VISIBLE);
- mNotificationHeader.setOnClickListener(listener);
+ mNotificationHeader.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
- mNotificationHeader, mNotificationParent);
+ mNotificationHeader, mContainingNotification);
addView(mNotificationHeader, 0);
invalidate();
} else {
header.reapply(getContext(), mNotificationHeader);
- mNotificationHeaderWrapper.notifyContentUpdated(notification, mIsLowPriority);
}
+ mNotificationHeaderWrapper.notifyContentUpdated(mContainingNotification);
+ recreateLowPriorityHeader(builder);
+ updateHeaderVisibility(false /* animate */);
updateChildrenHeaderAppearance();
}
+ /**
+ * Recreate the low-priority header.
+ *
+ * @param builder a builder to reuse. Otherwise the builder will be recovered.
+ */
+ private void recreateLowPriorityHeader(Notification.Builder builder) {
+ RemoteViews header;
+ StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
+ if (mIsLowPriority) {
+ if (builder == null) {
+ builder = Notification.Builder.recoverBuilder(getContext(),
+ notification.getNotification());
+ }
+ header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ if (mNotificationHeaderLowPriority == null) {
+ mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(),
+ this);
+ final View expandButton = mNotificationHeaderLowPriority.findViewById(
+ com.android.internal.R.id.expand_button);
+ expandButton.setVisibility(VISIBLE);
+ mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
+ mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
+ mNotificationHeaderLowPriority, mContainingNotification);
+ addView(mNotificationHeaderLowPriority, 0);
+ invalidate();
+ } else {
+ header.reapply(getContext(), mNotificationHeaderLowPriority);
+ }
+ mNotificationHeaderWrapperLowPriority.notifyContentUpdated(mContainingNotification);
+ } else {
+ mNotificationHeaderLowPriority = null;
+ mNotificationHeaderWrapperLowPriority = null;
+ }
+ }
+
public void updateChildrenHeaderAppearance() {
mHeaderUtil.updateChildrenHeaderAppearance();
}
@@ -371,9 +429,6 @@
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
- if (mIsLowPriority && !mChildrenExpanded) {
- return mNotificationHeader.getHeight();
- }
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
return getIntrinsicHeight(maxAllowedVisibleChildren);
}
@@ -383,6 +438,9 @@
* in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
+ if (showingAsLowPriority()) {
+ return mNotificationHeaderLowPriority.getHeight();
+ }
int intrinsicHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
int childCount = mChildren.size();
@@ -442,24 +500,25 @@
int lastVisibleIndex = maxAllowedVisibleChildren - 1;
int firstOverflowIndex = lastVisibleIndex + 1;
float expandFactor = 0;
+ boolean expandingToExpandedGroup = mUserLocked && !showingAsLowPriority();
if (mUserLocked) {
expandFactor = getGroupExpandFraction();
firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
}
- boolean childrenExpanded = !mNotificationParent.isGroupExpansionChanging()
+ boolean childrenExpanded = !mContainingNotification.isGroupExpansionChanging()
&& mChildrenExpanded;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (!firstChild) {
- if (mUserLocked) {
+ if (expandingToExpandedGroup) {
yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
expandFactor);
} else {
yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
}
} else {
- if (mUserLocked) {
+ if (expandingToExpandedGroup) {
yPosition += NotificationUtils.interpolate(
0,
mNotificatonTopPadding + mDividerHeight,
@@ -478,7 +537,7 @@
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
childState.zTranslation = childrenExpanded
- ? mNotificationParent.getTranslationZ()
+ ? mContainingNotification.getTranslationZ()
: 0;
childState.dimmed = parentState.dimmed;
childState.dark = parentState.dark;
@@ -487,7 +546,7 @@
childState.clipTopAmount = 0;
childState.alpha = 0;
if (i < firstOverflowIndex) {
- childState.alpha = mIsLowPriority && !mChildrenExpanded ? expandFactor : 1.0f;
+ childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
@@ -526,7 +585,7 @@
}
mHeaderViewState.initFrom(mNotificationHeader);
mHeaderViewState.zTranslation = childrenExpanded
- ? mNotificationParent.getTranslationZ()
+ ? mContainingNotification.getTranslationZ()
: 0;
}
}
@@ -561,11 +620,11 @@
}
private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
- if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
+ if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())) {
return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
}
- if (!mNotificationParent.isOnKeyguard()
- && (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp())) {
+ if (mIsLowPriority || !mContainingNotification.isOnKeyguard()
+ && (mContainingNotification.isExpanded() || mContainingNotification.isHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
}
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
@@ -578,8 +637,8 @@
if (mUserLocked) {
expandFraction = getGroupExpandFraction();
}
- final boolean dividersVisible = mUserLocked
- || mNotificationParent.isGroupExpansionChanging();
+ final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
+ || mContainingNotification.isGroupExpansionChanging();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
@@ -590,7 +649,7 @@
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
- if (mUserLocked && viewState.alpha != 0) {
+ if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
alpha = NotificationUtils.interpolate(0, 0.5f,
Math.min(viewState.alpha, expandFraction));
}
@@ -612,7 +671,7 @@
private void updateChildrenClipping() {
int childCount = mChildren.size();
- int layoutEnd = mNotificationParent.getActualHeight() - mClipBottomAmount;
+ int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (child.getVisibility() == GONE) {
@@ -652,8 +711,8 @@
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
float expandFraction = getGroupExpandFraction();
- final boolean dividersVisible = mUserLocked
- || mNotificationParent.isGroupExpansionChanging();
+ final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
+ || mContainingNotification.isGroupExpansionChanging();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
@@ -664,7 +723,7 @@
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
- if (mUserLocked && viewState.alpha != 0) {
+ if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
alpha = NotificationUtils.interpolate(0, 0.5f,
Math.min(viewState.alpha, expandFraction));
}
@@ -718,23 +777,100 @@
}
}
- public void setNotificationParent(ExpandableNotificationRow parent) {
- mNotificationParent = parent;
- mHeaderUtil = new NotificationHeaderUtil(mNotificationParent);
+ public void setContainingNotification(ExpandableNotificationRow parent) {
+ mContainingNotification = parent;
+ mHeaderUtil = new NotificationHeaderUtil(mContainingNotification);
}
- public ExpandableNotificationRow getNotificationParent() {
- return mNotificationParent;
+ public ExpandableNotificationRow getContainingNotification() {
+ return mContainingNotification;
}
public NotificationHeaderView getHeaderView() {
return mNotificationHeader;
}
- public void updateHeaderVisibility(int visiblity) {
- if (mNotificationHeader != null) {
- mNotificationHeader.setVisibility(visiblity);
+ public NotificationHeaderView getLowPriorityHeaderView() {
+ return mNotificationHeaderLowPriority;
+ }
+
+ public void setHeaderVisible(boolean visible) {
+ mHeaderVisible = visible;
+ updateHeaderVisibility(false /* animate */);
+ }
+
+ private void updateHeaderVisibility(boolean animate) {
+ NotificationHeaderView visibleHeader = mNotificationHeader;
+ NotificationHeaderView hiddenHeader = mNotificationHeaderLowPriority;
+ boolean normalHeaderVisible = true;
+ if (showingAsLowPriority()) {
+ visibleHeader = mNotificationHeaderLowPriority;
+ hiddenHeader = mNotificationHeader;
+ normalHeaderVisible = false;
}
+ if (animate) {
+ if (mHeaderVisible && visibleHeader != null && hiddenHeader != null
+ && mShowingNormalHeader != normalHeaderVisible) {
+ hiddenHeader.setVisibility(VISIBLE);
+ visibleHeader.setVisibility(VISIBLE);
+ NotificationViewWrapper visibleWrapper = getWrapperForView(visibleHeader);
+ NotificationViewWrapper hiddenWrapper = getWrapperForView(hiddenHeader);
+ visibleWrapper.transformFrom(hiddenWrapper);
+ hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
+ startChildAlphaAnimations(normalHeaderVisible);
+ } else {
+ animate = false;
+ }
+ }
+ if (!animate) {
+ if (visibleHeader != null) {
+ getWrapperForView(visibleHeader).setVisible(true);
+ visibleHeader.setVisibility(mHeaderVisible ? VISIBLE : INVISIBLE);
+ }
+ if (hiddenHeader != null) {
+ getWrapperForView(hiddenHeader).setVisible(false);
+ hiddenHeader.setVisibility(INVISIBLE);
+ }
+ }
+ mShowingNormalHeader = normalHeaderVisible;
+ }
+
+ private void startChildAlphaAnimations(boolean toVisible) {
+ float target = toVisible ? 1.0f : 0.0f;
+ float start = 1.0f - target;
+ int childCount = mChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) {
+ break;
+ }
+ ExpandableNotificationRow child = mChildren.get(i);
+ child.setAlpha(start);
+ ViewState viewState = new ViewState();
+ viewState.initFrom(child);
+ viewState.alpha = target;
+ ALPHA_FADE_IN.setDelay(i * 50);
+ viewState.animateTo(child, ALPHA_FADE_IN);
+ }
+ }
+
+
+ private void updateHeaderTransformation() {
+ if (mUserLocked && mHeaderVisible && showingAsLowPriority()) {
+ float fraction = getGroupExpandFraction();
+ mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority,
+ fraction);
+ mNotificationHeader.setVisibility(VISIBLE);
+ mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper,
+ fraction);
+ }
+
+ }
+
+ private NotificationViewWrapper getWrapperForView(NotificationHeaderView visibleHeader) {
+ if (visibleHeader == mNotificationHeader) {
+ return mNotificationHeaderWrapper;
+ }
+ return mNotificationHeaderWrapperLowPriority;
}
/**
@@ -746,7 +882,7 @@
if (mNotificationHeader != null) {
if (expanded) {
ColorDrawable cd = new ColorDrawable();
- cd.setColor(mNotificationParent.calculateBgColor());
+ cd.setColor(mContainingNotification.calculateBgColor());
mNotificationHeader.setHeaderBackgroundDrawable(cd);
} else {
mNotificationHeader.setHeaderBackgroundDrawable(null);
@@ -755,6 +891,10 @@
}
public int getMaxContentHeight() {
+ if (showingAsLowPriority()) {
+ return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true
+ /* likeHighPriority */);
+ }
int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
int visibleChildren = 0;
int childCount = mChildren.size();
@@ -781,13 +921,21 @@
}
mActualHeight = actualHeight;
float fraction = getGroupExpandFraction();
+ boolean showingLowPriority = showingAsLowPriority();
+ updateHeaderTransformation();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
- float childHeight = child.isExpanded(true /* allowOnKeyguard */)
- ? child.getMaxExpandHeight()
- : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
+ float childHeight;
+ if (showingLowPriority) {
+ childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */);
+ } else if (child.isExpanded(true /* allowOnKeyguard */)) {
+ childHeight = child.getMaxExpandHeight();
+ } else {
+ childHeight = child.getShowingLayout().getMinHeight(
+ true /* likeGroupExpanded */);
+ }
if (i < maxAllowedVisibleChildren) {
float singleLineHeight = child.getShowingLayout().getMinHeight(
false /* likeGroupExpanded */);
@@ -800,7 +948,8 @@
}
public float getGroupExpandFraction() {
- int visibleChildrenExpandedHeight = getVisibleChildrenExpandHeight();
+ int visibleChildrenExpandedHeight = showingAsLowPriority() ? getMaxContentHeight()
+ : getVisibleChildrenExpandHeight();
int minExpandHeight = getCollapsedHeight();
float factor = (mActualHeight - minExpandHeight)
/ (float) (visibleChildrenExpandedHeight - minExpandHeight);
@@ -827,16 +976,23 @@
}
public int getMinHeight() {
- return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED);
+ return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
}
public int getCollapsedHeight() {
- return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */));
+ return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
+ false /* likeHighPriority */);
}
- private int getMinHeight(int maxAllowedVisibleChildren) {
- if (mIsLowPriority && !mChildrenExpanded) {
- return mNotificationHeader.getHeight();
+ /**
+ * Get the minimum Height for this group.
+ *
+ * @param maxAllowedVisibleChildren the number of children that should be visible
+ * @param likeHighPriority if the height should be calculated as if it were not low priority
+ */
+ private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
+ if (!likeHighPriority && showingAsLowPriority()) {
+ return mNotificationHeaderLowPriority.getHeight();
}
int minExpandHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
@@ -859,6 +1015,10 @@
return minExpandHeight;
}
+ public boolean showingAsLowPriority() {
+ return mIsLowPriority && !mContainingNotification.isExpanded();
+ }
+
public void setDark(boolean dark, boolean fade, long delay) {
if (mOverflowNumber != null) {
mOverflowInvertHelper.setInverted(dark, fade, delay);
@@ -867,9 +1027,15 @@
}
public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
- removeView(mNotificationHeader);
- mNotificationHeader = null;
- recreateNotificationHeader(listener, notification);
+ if (mNotificationHeader != null) {
+ removeView(mNotificationHeader);
+ mNotificationHeader = null;
+ }
+ if (mNotificationHeaderLowPriority != null) {
+ removeView(mNotificationHeaderLowPriority);
+ mNotificationHeaderLowPriority = null;
+ }
+ recreateNotificationHeader(listener);
initDimens();
for (int i = 0; i < mDividers.size(); i++) {
View prevDivider = mDividers.get(i);
@@ -888,16 +1054,19 @@
public void setUserLocked(boolean userLocked) {
mUserLocked = userLocked;
+ if (!mUserLocked) {
+ updateHeaderVisibility(false /* animate */);
+ }
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
- child.setUserLocked(userLocked);
+ child.setUserLocked(userLocked && !showingAsLowPriority());
}
}
public void onNotificationUpdated() {
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
- mNotificationParent.getNotificationColor());
+ mContainingNotification.getNotificationColor());
}
public int getPositionInLinearLayout(View childInGroup) {
@@ -926,6 +1095,13 @@
header.getIcon().setForceHidden(!iconsVisible);
}
}
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ NotificationHeaderView header
+ = mNotificationHeaderWrapperLowPriority.getNotificationHeader();
+ if (header != null) {
+ header.getIcon().setForceHidden(!iconsVisible);
+ }
+ }
}
public void setClipBottomAmount(int clipBottomAmount) {
@@ -935,5 +1111,36 @@
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
+ if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
+ recreateLowPriorityHeader(null /* existingBuilder */);
+ updateHeaderVisibility(false /* animate */);
+ }
+ if (mUserLocked) {
+ setUserLocked(mUserLocked);
+ }
+ }
+
+ public NotificationHeaderView getVisibleHeader() {
+ NotificationHeaderView header = mNotificationHeader;
+ if (showingAsLowPriority()) {
+ header = mNotificationHeaderLowPriority;
+ }
+ return header;
+ }
+
+ public void onExpansionChanged() {
+ if (mIsLowPriority) {
+ if (mUserLocked) {
+ setUserLocked(mUserLocked);
+ }
+ updateHeaderVisibility(true /* animate */);
+ }
+ }
+
+ public float getIncreasedPaddingAmount() {
+ if (showingAsLowPriority()) {
+ return 0.0f;
+ }
+ return getGroupExpandFraction();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 48dbe49..d3b336b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3416,6 +3416,7 @@
for (View view : mClearOverlayViewsWhenFinished) {
StackStateAnimator.removeFromOverlay(view);
}
+ mClearOverlayViewsWhenFinished.clear();
}
private void runAnimationFinishedRunnables() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index e0a6159..c8659fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -136,7 +136,7 @@
yTranslation = view.getTranslationY();
zTranslation = view.getTranslationZ();
gone = view.getVisibility() == View.GONE;
- hidden = false;
+ hidden = view.getVisibility() == View.INVISIBLE;
scaleX = view.getScaleX();
scaleY = view.getScaleY();
}
@@ -594,7 +594,7 @@
}
protected void onYTranslationAnimationFinished(View view) {
- if (hidden) {
+ if (hidden && !gone) {
view.setVisibility(View.INVISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 15ad0ce..5911766 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -36,19 +36,19 @@
nm.createNotificationChannels(Arrays.asList(
new NotificationChannel(
ALERTS,
- context.getString(R.string.notification_channel_alerts),
+ R.string.notification_channel_alerts,
NotificationManager.IMPORTANCE_HIGH),
new NotificationChannel(
SCREENSHOTS,
- context.getString(R.string.notification_channel_screenshot),
+ R.string.notification_channel_screenshot,
NotificationManager.IMPORTANCE_LOW),
new NotificationChannel(
GENERAL,
- context.getString(R.string.notification_channel_general),
+ R.string.notification_channel_general,
NotificationManager.IMPORTANCE_MIN),
new NotificationChannel(
STORAGE,
- context.getString(R.string.notification_channel_storage),
+ R.string.notification_channel_storage,
NotificationManager.IMPORTANCE_LOW)
));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index d07cea1d..f016aa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -52,11 +52,11 @@
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.custom_view_dark);
View v = views.apply(mContext, null);
NotificationViewWrapper wrap = NotificationCustomViewWrapper.wrap(mContext, v, mRow);
- wrap.notifyContentUpdated(null, false /* isLowPriority */);
+ wrap.notifyContentUpdated(mRow);
Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
views.reapply(mContext, v);
- wrap.notifyContentUpdated(null, false /* isLowPriority */);
+ wrap.notifyContentUpdated(mRow);
Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 0491fc4..92f8c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -19,6 +19,8 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -98,7 +100,6 @@
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1);
}
@@ -169,6 +170,28 @@
@Test
@UiThreadTest
+ public void testBindNotification_SetsGroupName_resId() throws Exception {
+ when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+ eq(R.string.legacy_vpn_name), anyObject())).thenReturn(
+ getContext().getString(R.string.legacy_vpn_name));
+ mNotificationChannel.setGroup("test_group_id");
+ final NotificationChannelGroup notificationChannelGroup =
+ new NotificationChannelGroup("test_group_id", R.string.legacy_vpn_name);
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(notificationChannelGroup);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(View.VISIBLE, groupNameView.getVisibility());
+ assertEquals(mContext.getString(R.string.legacy_vpn_name), groupNameView.getText());
+ final TextView groupDividerView =
+ (TextView) mNotificationInfo.findViewById(R.id.pkg_group_divider);
+ assertEquals(View.VISIBLE, groupDividerView.getVisibility());
+ }
+
+ @Test
+ @UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
@@ -178,6 +201,22 @@
@Test
@UiThreadTest
+ public void testBindNotification_SetsTextChannelName_resId() throws Exception {
+ when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+ eq(R.string.notification_menu_accessibility), anyObject())).thenReturn(
+ getContext().getString(R.string.notification_menu_accessibility));
+ NotificationChannel notificationChannelResId = new NotificationChannel(
+ TEST_CHANNEL, R.string.notification_menu_accessibility,
+ NotificationManager.IMPORTANCE_LOW);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, notificationChannelResId, null, null, null);
+ final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
+ assertEquals(getContext().getString(R.string.notification_menu_accessibility),
+ textView.getText());
+ }
+
+ @Test
+ @UiThreadTest
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index ba20999..e47f750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -12,6 +12,7 @@
import com.android.settingslib.net.DataUsageController;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -106,6 +107,7 @@
TelephonyIcons.QS_DATA_4G);
}
+ @Ignore("Flaky")
@Test
public void testDataDisabledIcon() {
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 2c0f9c9..1555856 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -94,6 +94,14 @@
}
@Test
+ public void testNoEmergencyOnlyWrongSubscription() {
+ setupDefaultSignal();
+ setDefaultSubId(42);
+ mNetworkController.recalculateEmergency();
+ verifyEmergencyOnly(false);
+ }
+
+ @Test
public void testNoEmengencyNoSubscriptions() {
setupDefaultSignal();
setSubscriptions();
@@ -286,12 +294,12 @@
for (int i = 0; i < testSubscriptions.length; i++) {
if (i == indexToSkipController) {
// Make sure a controller was created despite us not adding one.
- assertTrue(mNetworkController.mMobileSignalControllers.containsKey(
- testSubscriptions[i]));
+ assertTrue(mNetworkController.mMobileSignalControllers.indexOfKey(
+ testSubscriptions[i]) >= 0);
} else if (i == indexToSkipSubscription) {
// Make sure the controller that did exist was removed
- assertFalse(mNetworkController.mMobileSignalControllers.containsKey(
- testSubscriptions[i]));
+ assertFalse(mNetworkController.mMobileSignalControllers.indexOfKey(
+ testSubscriptions[i]) >= 0);
} else {
// If a MobileSignalController is around it needs to not be unregistered.
Mockito.verify(mobileSignalControllers[i], Mockito.never())
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4bacb58..2b219e6 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2157,7 +2157,7 @@
// ACTION: Settings -> Select summary tab.
// CATEGORY: SETTINGS
- ACTION_SELECT_SUMMARY=476;
+ ACTION_SELECT_SUMMARY = 476;
// ACTION: Settings -> Select support tab.
// CATEGORY: SETTINGS
@@ -2208,7 +2208,7 @@
NIGHT_DISPLAY_SETTINGS = 488;
// ACTION: Settings -> Storage -> Manage storage -> Click Storage Manager
- // SUBTYPE: false is off, true is on
+ // SUBTYPE: false is off, true is on
ACTION_TOGGLE_STORAGE_MANAGER = 489;
// Settings launched from collapsed quick settings.
@@ -3477,6 +3477,15 @@
// OS: O
WIFI_NETWORK_DETAILS = 849;
+ // ACTION: Settings > Battery > Menu > Usage Alerts
+ ACTION_SETTINGS_MENU_BATTERY_USAGE_ALERTS = 850;
+
+ // ACTION: Settings > Battery > Menu > Optimization
+ ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION = 851;
+
+ // ACTION: Settings > Battery > Menu > Apps Toggle
+ ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE = 852;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 7a85d4e..8947b7d 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -186,6 +186,29 @@
}
// Called by Shell command.
+ void destroySessions(int userId, IResultReceiver receiver) {
+ Slog.i(TAG, "destroySessions() for userId " + userId);
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ synchronized (mLock) {
+ if (userId != UserHandle.USER_ALL) {
+ mServicesCache.get(userId).destroySessionsLocked();
+ } else {
+ final int size = mServicesCache.size();
+ for (int i = 0; i < size; i++) {
+ mServicesCache.valueAt(i).destroySessionsLocked();
+ }
+ }
+ }
+
+ try {
+ receiver.send(0, new Bundle());
+ } catch (RemoteException e) {
+ // Just ignore it...
+ }
+ }
+
+ // Called by Shell command.
void listSessions(int userId, IResultReceiver receiver) {
Slog.i(TAG, "listSessions() for userId " + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index bfc6e83..5e852f1 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -59,7 +59,6 @@
import android.service.autofill.SaveInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.LocalLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -405,6 +404,12 @@
}
}
+ void destroySessionsLocked() {
+ for (Session session : mSessions.values()) {
+ session.removeSelf();
+ }
+ }
+
void listSessionsLocked(ArrayList<String> output) {
for (IBinder activityToken : mSessions.keySet()) {
output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
@@ -666,6 +671,28 @@
// FillServiceCallbacks
@Override
+ public void onDisableSelf() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final String autoFillService = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.AUTO_FILL_SERVICE, mUserId);
+ if (mInfo.getServiceInfo().getComponentName().equals(
+ ComponentName.unflattenFromString(autoFillService))) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.AUTO_FILL_SERVICE, null, mUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ synchronized (mLock) {
+ destroyLocked();
+ mSessions.remove(this);
+ }
+ }
+
+ // FillServiceCallbacks
+ @Override
public void onServiceDied(RemoteFillService service) {
// TODO(b/33197203): implement
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index cfa4a1d..76eed2a 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -20,7 +20,6 @@
import android.app.ActivityManager;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -50,6 +49,8 @@
return requestSave();
case "list":
return requestList(pw);
+ case "destroy":
+ return requestDestroy(pw);
case "reset":
return requestReset();
default:
@@ -67,6 +68,9 @@
pw.println(" list sessions [--user USER_ID]");
pw.println(" List all pending sessions.");
pw.println("");
+ pw.println(" destroy sessions [--user USER_ID]");
+ pw.println(" Destroy all pending sessions.");
+ pw.println("");
pw.println(" save [--user USER_ID]");
pw.println(" Request provider to save contents of the top activity. ");
pw.println("");
@@ -82,30 +86,55 @@
return 0;
}
- private int requestList(PrintWriter pw) {
- final String type = getNextArgRequired();
- if (!type.equals("sessions")) {
- pw.println("Error: invalid list type");
+ private int requestDestroy(PrintWriter pw) {
+ if (!isNextArgSessions(pw)) {
return -1;
-
}
+
final int userId = getUserIdFromArgsOrAllUsers();
final CountDownLatch latch = new CountDownLatch(1);
final IResultReceiver receiver = new IResultReceiver.Stub() {
-
@Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
+ public void send(int resultCode, Bundle resultData) {
+ latch.countDown();
+ }
+ };
+ return requestSessionCommon(pw, latch, () -> mService.destroySessions(userId, receiver));
+ }
+
+ private int requestList(PrintWriter pw) {
+ if (!isNextArgSessions(pw)) {
+ return -1;
+ }
+
+ final int userId = getUserIdFromArgsOrAllUsers();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IResultReceiver receiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
final ArrayList<String> sessions = resultData
.getStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS);
-
for (String session : sessions) {
pw.println(session);
}
latch.countDown();
}
};
+ return requestSessionCommon(pw, latch, () -> mService.listSessions(userId, receiver));
+ }
- mService.listSessions(userId, receiver);
+ private boolean isNextArgSessions(PrintWriter pw) {
+ final String type = getNextArgRequired();
+ if (!type.equals("sessions")) {
+ pw.println("Error: invalid list type");
+ return false;
+ }
+ return true;
+ }
+
+ private int requestSessionCommon(PrintWriter pw, CountDownLatch latch,
+ Runnable command) {
+ command.run();
try {
final boolean received = latch.await(5, TimeUnit.SECONDS);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index c469718..04eeb32 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -33,6 +33,7 @@
import android.service.autofill.AutoFillService;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
+import android.service.autofill.IAutoFillServiceConnection;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.text.format.DateUtils;
@@ -57,7 +58,7 @@
private static final boolean DEBUG = Helper.DEBUG;
// How long after the last interaction with the service we would unbind
- private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS;
+ private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private final Context mContext;
@@ -91,6 +92,7 @@
void onSaveRequestSuccess();
void onSaveRequestFailure(CharSequence message);
void onServiceDied(RemoteFillService service);
+ void onDisableSelf();
}
public RemoteFillService(Context context, ComponentName componentName,
@@ -197,6 +199,10 @@
}
}
+ private void handleDisableSelf() {
+ mCallbacks.onDisableSelf();
+ }
+
private boolean isBound() {
return mAutoFillService != null;
}
@@ -239,7 +245,7 @@
// Need to double check if it's null, since it could be set on onServiceDisconnected()
if (mAutoFillService != null) {
try {
- mAutoFillService.onDisconnected();
+ mAutoFillService.onInit(null);
} catch (Exception e) {
Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
}
@@ -321,7 +327,12 @@
// Need to double check if it's null, since it could be set on
// onServiceDisconnected()
if (mAutoFillService != null) {
- mAutoFillService.onConnected();
+ mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
+ @Override
+ public void disableSelf() {
+ mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
+ }
+ });
}
} catch (RemoteException e) {
Slog.w(LOG_TAG, "Exception calling onConnected(): " + e);
@@ -347,6 +358,7 @@
public static final int MSG_BINDER_DIED = 2;
public static final int MSG_UNBIND = 3;
public static final int MSG_ON_PENDING_REQUEST = 4;
+ public static final int MSG_ON_DISABLE_SELF = 5;
public MyHandler(Context context) {
// Cannot use lambda - doesn't compile
@@ -374,6 +386,10 @@
case MSG_ON_PENDING_REQUEST: {
handlePendingRequest((PendingRequest) message.obj);
} break;
+
+ case MSG_ON_DISABLE_SELF: {
+ handleDisableSelf();
+ } break;
}
}
}, false);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 949a80c..0da36c8 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -26,6 +26,7 @@
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
import android.text.TextUtils;
+import android.util.Slog;
import android.view.autofill.AutoFillId;
import android.widget.Toast;
@@ -41,6 +42,8 @@
* managing saving of user edits.
*/
public final class AutoFillUI {
+ private static final String TAG = "AutoFillUI";
+
private final Handler mHandler = UiThread.getHandler();
private final @NonNull Context mContext;
@@ -191,9 +194,17 @@
}
@Override
- public void onCancel() {
+ public void onCancel(IntentSender listener) {
// TODO(b/33197203): add MetricsLogger call
hideSaveUiUiThread();
+ if (listener != null) {
+ try {
+ listener.sendIntent(mContext, 0, null, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(TAG, "Error starting negative action listener: "
+ + listener, e);
+ }
+ }
}
});
});
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index afe93c7..d443dc3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Context;
+import android.content.IntentSender;
import android.os.Handler;
import android.service.autofill.SaveInfo;
import android.text.format.DateUtils;
@@ -38,7 +39,7 @@
final class SaveUi {
public interface OnSaveListener {
void onSave();
- void onCancel();
+ void onCancel(IntentSender listener);
}
private static final long LIFETIME_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
@@ -87,14 +88,20 @@
subTitleView.setVisibility(View.VISIBLE);
}
- final View noButton = view.findViewById(R.id.autofill_save_no);
- noButton.setOnClickListener((v) -> mListener.onCancel());
+ final TextView noButton = view.findViewById(R.id.autofill_save_no);
+ if (info.getNegativeActionTitle() != null) {
+ noButton.setText(info.getNegativeActionTitle());
+ noButton.setOnClickListener((v) -> mListener.onCancel(
+ info.getNegativeActionListener()));
+ } else {
+ noButton.setOnClickListener((v) -> mListener.onCancel(null));
+ }
final View yesButton = view.findViewById(R.id.autofill_save_yes);
yesButton.setOnClickListener((v) -> mListener.onSave());
final View closeButton = view.findViewById(R.id.autofill_save_close);
- closeButton.setOnClickListener((v) -> mListener.onCancel());
+ closeButton.setOnClickListener((v) -> mListener.onCancel(null));
mDialog = new Dialog(context, R.style.Theme_Material_Panel);
mDialog.setContentView(view);
@@ -112,7 +119,7 @@
mDialog.show();
- mHandler.postDelayed(() -> mListener.onCancel(), LIFETIME_MILLIS);
+ mHandler.postDelayed(() -> mListener.onCancel(null), LIFETIME_MILLIS);
}
void destroy() {
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 4f8b8af..2d947a4 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -16,9 +16,23 @@
package com.android.server.backup;
-import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
-import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_POLICY_ALLOW_APKS;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_MANIFEST_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
import android.app.ActivityManager;
@@ -2374,6 +2388,8 @@
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ monitor = monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
+ null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
@@ -3338,6 +3354,14 @@
addBackupTrace("illegal key " + key + " from " + pkgName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
"bad key");
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY,
+ mCurrentPackage,
+ BackupManagerMonitor
+ .LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_ILLEGAL_KEY,
+ key));
mBackupHandler.removeMessages(MSG_BACKUP_OPERATION_TIMEOUT);
sendBackupOnPackageResult(mObserver, pkgName,
BackupManager.ERROR_AGENT_FAILURE);
@@ -3407,6 +3431,11 @@
if (MORE_DEBUG) Slog.i(TAG,
"no backup data written; not calling transport");
addBackupTrace("no data to send");
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
}
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -3502,8 +3531,10 @@
Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
mMonitor = monitorEvent(mMonitor,
- BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+ putMonitoringExtra(null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL,
+ mCancelAll));
addBackupTrace(
"cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
errorCleanup();
@@ -4593,6 +4624,11 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring ineligible package " + pkg);
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -4603,6 +4639,11 @@
Slog.d(TAG, "Ignoring full-data backup of key/value participant "
+ pkg);
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -4613,6 +4654,11 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring stopped package " + pkg);
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
sendBackupOnPackageResult(mBackupObserver, pkg,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -4620,7 +4666,7 @@
mPackages.add(info);
} catch (NameNotFoundException e) {
Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
- monitor = monitorEvent(monitor,
+ mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
mCurrentPackage,
BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -4696,9 +4742,17 @@
if (!mEnabled || !mProvisioned) {
// Backups are globally disabled, so don't proceed.
if (DEBUG) {
- Slog.i(TAG, "full backup requested but e=" + mEnabled
- + " p=" + mProvisioned + "; ignoring");
+ Slog.i(TAG, "full backup requested but enabled=" + mEnabled
+ + " provisioned=" + mProvisioned + "; ignoring");
}
+ int monitoringEvent;
+ if (!mEnabled) {
+ monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED;
+ } else {
+ monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
+ }
+ mMonitor = monitorEvent(mMonitor, monitoringEvent, null,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
mUpdateSchedule = false;
backupRunStatus = BackupManager.ERROR_BACKUP_NOT_ALLOWED;
return;
@@ -4722,7 +4776,8 @@
PackageInfo currentPackage = mPackages.get(i);
String packageName = currentPackage.packageName;
if (DEBUG) {
- Slog.i(TAG, "Initiating full-data transport backup of " + packageName);
+ Slog.i(TAG, "Initiating full-data transport backup of " + packageName
+ + " token: " + mCurrentOpToken);
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_PACKAGE, packageName);
@@ -4781,6 +4836,13 @@
+ packageName + ": " + preflightResult
+ ", not running backup.");
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR,
+ preflightResult));
backupPackageStatus = (int) preflightResult;
} else {
int nRead = 0;
@@ -4808,6 +4870,11 @@
if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
Slog.w(TAG, "Package hit quota limit in-flight " + packageName
+ ": " + totalRead + " of " + quota);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ null);
mBackupRunner.sendQuotaExceeded(totalRead, quota);
}
}
@@ -4942,6 +5009,14 @@
} catch (Exception e) {
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
Slog.w(TAG, "Exception trying full transport backup", e);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP,
+ Log.getStackTraceString(e)));
+
} finally {
if (mCancelAll) {
@@ -5221,7 +5296,7 @@
}
mMonitor = monitorEvent(mMonitor,
- BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mIsCancelled = true;
// Cancel tasks spun off by this task.
@@ -6309,9 +6384,29 @@
} else {
Slog.w(TAG, "Metadata mismatch: package " + info.packageName
+ " but widget data for " + pkg);
+
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
}
} else {
Slog.w(TAG, "Unsupported metadata version " + version);
+
+ Bundle monitoringExtras = putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
+ info.packageName);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
}
}
@@ -6386,10 +6481,20 @@
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(TAG, "Package has restoreAnyVersion; taking data");
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_RESTORE_ANY_VERSION,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
policy = RestorePolicy.ACCEPT;
} else if (pkgInfo.versionCode >= version) {
Slog.i(TAG, "Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_VERSIONS_MATCH,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
} else {
// The data is from a newer version of the app than
// is presently installed. That means we can only
@@ -6403,30 +6508,43 @@
} else {
Slog.i(TAG, "Data requires newer version "
+ version + "; ignoring");
- ArrayList<Pair<String, String>> list =
- new ArrayList<>();
- list.add(new Pair<String, String>(
- EXTRA_LOG_OLD_VERSION,
- Integer.toString(version)));
mMonitor = monitorEvent(mMonitor,
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- list);
+ putMonitoringExtra(null,
+ EXTRA_LOG_OLD_VERSION,
+ version));
+
policy = RestorePolicy.IGNORE;
}
}
} else {
Slog.w(TAG, "Restore manifest signatures do not match "
+ "installed application for " + info.packageName);
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
}
} else {
Slog.w(TAG, "Package " + info.packageName
+ " is system level with no agent");
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_AGENT,
+ null);
}
} else {
if (DEBUG) Slog.i(TAG, "Restore manifest from "
+ info.packageName + " but allowBackup=false");
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
}
} catch (NameNotFoundException e) {
// Okay, the target app isn't installed. We can process
@@ -6439,26 +6557,71 @@
} else {
policy = RestorePolicy.IGNORE;
}
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_POLICY_ALLOW_APKS, mAllowApks);
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_APK_NOT_INSTALLED,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
}
if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) {
Slog.i(TAG, "Cannot restore package " + info.packageName
+ " without the matching .apk");
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
} else {
Slog.i(TAG, "Missing signature on backed-up package "
+ info.packageName);
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_MISSING_SIGNATURE,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
} else {
Slog.i(TAG, "Expected package " + info.packageName
+ " but restore manifest claims " + manifestPackage);
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
}
} else {
Slog.i(TAG, "Unknown restore manifest version " + version
+ " for package " + info.packageName);
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+
}
} catch (NumberFormatException e) {
Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
} catch (IllegalArgumentException e) {
Slog.w(TAG, e.getMessage());
}
@@ -8557,7 +8720,12 @@
return;
}
if (!PACKAGE_MANAGER_SENTINEL.equals(desc.getPackageName())) {
- Slog.e(TAG, "Required metadata but got " + desc.getPackageName());
+ Slog.e(TAG, "Required package metadata but got "
+ + desc.getPackageName());
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
mStatus = BackupTransport.TRANSPORT_ERROR;
executeNextState(UnifiedRestoreState.FINAL);
return;
@@ -8585,7 +8753,11 @@
// signature/version verification etc, so we simply do not proceed with
// the restore operation.
if (!mPmAgent.hasMetadata()) {
- Slog.e(TAG, "No restore metadata available, so not restoring");
+ Slog.e(TAG, "PM agent has no metadata, so not restoring");
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
PACKAGE_MANAGER_SENTINEL,
"Package manager restore metadata missing");
@@ -8601,6 +8773,10 @@
} catch (Exception e) {
// If we lost the transport at any time, halt
Slog.e(TAG, "Unable to contact transport for restore: " + e.getMessage());
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT,
+ null,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
mStatus = BackupTransport.TRANSPORT_ERROR;
mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
executeNextState(UnifiedRestoreState.FINAL);
@@ -8670,17 +8846,37 @@
// handle this case, we do not attempt the restore.
if ((mCurrentPackage.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
- String message = "Version " + metaInfo.versionCode
+ String message = "Source version " + metaInfo.versionCode
+ " > installed version " + mCurrentPackage.versionCode;
Slog.w(TAG, "Package " + pkgName + ": " + message);
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, false);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
pkgName, message);
nextState = UnifiedRestoreState.RUNNING_QUEUE;
return;
} else {
- if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
- + " > installed " + mCurrentPackage.versionCode
+ if (DEBUG) Slog.v(TAG, "Source version " + metaInfo.versionCode
+ + " > installed version " + mCurrentPackage.versionCode
+ " but restoreAnyVersion");
+ Bundle monitoringExtras = putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ monitoringExtras = putMonitoringExtra(monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, true);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
}
}
@@ -8739,6 +8935,9 @@
Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
Slog.w(TAG, "Signature mismatch restoring " + packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
"Signature mismatch");
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -11035,23 +11234,54 @@
}
}
+ private Bundle putMonitoringExtra(Bundle extras, String key, String value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putString(key, value);
+ return extras;
+ }
+
+ private Bundle putMonitoringExtra(Bundle extras, String key, int value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putInt(key, value);
+ return extras;
+ }
+
+ private Bundle putMonitoringExtra(Bundle extras, String key, long value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putLong(key, value);
+ return extras;
+ }
+
+
+ private Bundle putMonitoringExtra(Bundle extras, String key, boolean value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putBoolean(key, value);
+ return extras;
+ }
+
private static IBackupManagerMonitor monitorEvent(IBackupManagerMonitor monitor, int id,
- PackageInfo pkg, int category, ArrayList<Pair<String, String>> extras) {
+ PackageInfo pkg, int category, Bundle extras) {
if (monitor != null) {
try {
Bundle bundle = new Bundle();
bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id);
bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category);
if (pkg != null) {
- bundle.putString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME,
+ bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME,
pkg.packageName);
bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
pkg.versionCode);
}
if (extras != null) {
- for (Pair<String,String> pair : extras) {
- bundle.putString(pair.first, pair.second);
- }
+ bundle.putAll(extras);
}
monitor.onEvent(bundle);
return monitor;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index b9dbe13..619ddb1 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -41,6 +41,7 @@
import com.android.server.EventLogTags;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -103,13 +104,18 @@
// been removed from mBoundTransports because onServiceDisconnected would already been
// called on TransportConnection objects.
synchronized (mTransportLock) {
- for (ComponentName transport : mValidTransports.keySet()) {
- if (transport.getPackageName().equals(packageName)) {
- TransportConnection removed = mValidTransports.remove(transport);
- if (removed != null) {
- mContext.unbindService(removed);
- log_verbose("Package removed, Removing transport: " +
- transport.flattenToShortString());
+ Iterator<Map.Entry<ComponentName, TransportConnection>> iter =
+ mValidTransports.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<ComponentName, TransportConnection> validTransport = iter.next();
+ ComponentName componentName = validTransport.getKey();
+ if (componentName.getPackageName().equals(packageName)) {
+ TransportConnection transportConnection = validTransport.getValue();
+ iter.remove();
+ if (transportConnection != null) {
+ mContext.unbindService(transportConnection);
+ log_verbose("Package removed, removing transport: "
+ + componentName.flattenToShortString());
}
}
}
@@ -278,15 +284,16 @@
}
private void tryBindTransport(ServiceInfo transport) {
- Slog.d(TAG, "Binding to transport: " + transport.getComponentName().flattenToShortString());
+ final ComponentName transportComponentName = transport.getComponentName();
+ Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
// TODO: b/22388012 (Multi user backup and restore)
- TransportConnection connection = new TransportConnection(transport.getComponentName());
- if (bindToTransport(transport.getComponentName(), connection)) {
+ TransportConnection connection = new TransportConnection(transportComponentName);
+ if (bindToTransport(transportComponentName, connection)) {
synchronized (mTransportLock) {
- mValidTransports.put(transport.getComponentName(), connection);
+ mValidTransports.put(transportComponentName, connection);
}
} else {
- Slog.w(TAG, "Couldn't bind to transport " + transport.getComponentName());
+ Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
}
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 02223c1..0d438cb 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -99,6 +99,7 @@
static final boolean DEBUG_VALIDATE = localLOGV || false;
static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
+ static final boolean DEBUG_WAKELOCK = localLOGV || false;
static final boolean RECORD_ALARMS_IN_HISTORY = true;
static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
static final int ALARM_EVENT = 1;
@@ -856,6 +857,19 @@
mFilterStats = fs;
mAlarmType = alarmType;
}
+
+ @Override
+ public String toString() {
+ return "InFlight{"
+ + "pendingIntent=" + mPendingIntent
+ + ", workSource=" + mWorkSource
+ + ", uid=" + mUid
+ + ", tag=" + mTag
+ + ", broadcastStats=" + mBroadcastStats
+ + ", filterStats=" + mFilterStats
+ + ", alarmType=" + mAlarmType
+ + "}";
+ }
}
static final class FilterStats {
@@ -873,6 +887,19 @@
mBroadcastStats = broadcastStats;
mTag = tag;
}
+
+ @Override
+ public String toString() {
+ return "FilterStats{"
+ + "tag=" + mTag
+ + ", lastTime=" + lastTime
+ + ", aggregateTime=" + aggregateTime
+ + ", count=" + count
+ + ", numWakeup=" + numWakeup
+ + ", startTime=" + startTime
+ + ", nesting=" + nesting
+ + "}";
+ }
}
static final class BroadcastStats {
@@ -890,6 +917,19 @@
mUid = uid;
mPackageName = packageName;
}
+
+ @Override
+ public String toString() {
+ return "BroadcastStats{"
+ + "uid=" + mUid
+ + ", packageName=" + mPackageName
+ + ", aggregateTime=" + aggregateTime
+ + ", count=" + count
+ + ", numWakeup=" + numWakeup
+ + ", startTime=" + startTime
+ + ", nesting=" + nesting
+ + "}";
+ }
}
final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
@@ -2895,6 +2935,9 @@
updateStatsLocked(inflight);
}
mBroadcastRefCount--;
+ if (DEBUG_WAKELOCK) {
+ Slog.d(TAG, "mBroadcastRefCount -> " + mBroadcastRefCount);
+ }
if (mBroadcastRefCount == 0) {
mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
mWakeLock.release();
@@ -3035,6 +3078,9 @@
}
// The alarm is now in flight; now arrange wakelock and stats tracking
+ if (DEBUG_WAKELOCK) {
+ Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
+ }
if (mBroadcastRefCount == 0) {
setWakelockWorkSource(alarm.operation, alarm.workSource,
alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cace7a9..54ee5dc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,8 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -144,6 +146,8 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static java.lang.Integer.MAX_VALUE;
+
import android.Manifest;
import android.Manifest.permission;
import android.annotation.NonNull;
@@ -3072,7 +3076,7 @@
final IVoiceInteractionSession session;
if (mLastResumedActivity.task != null
- && mLastResumedActivity.task.voiceSession != null) {
+ && mLastResumedActivity.task.voiceSession != null) {
session = mLastResumedActivity.task.voiceSession;
} else {
session = mLastResumedActivity.voiceSession;
@@ -4157,13 +4161,9 @@
synchronized (this) {
for (int i=mLruProcesses.size()-1; i>=0; i--) {
final ProcessRecord proc = mLruProcesses.get(i);
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT
- || procState > proc.setProcState) {
- if (proc.pkgList.containsKey(packageName)) {
- procState = proc.setProcState;
- break;
- }
- if (proc.pkgDeps != null && proc.pkgDeps.contains(packageName)) {
+ if (procState > proc.setProcState) {
+ if (proc.pkgList.containsKey(packageName) ||
+ (proc.pkgDeps != null && proc.pkgDeps.contains(packageName))) {
procState = proc.setProcState;
}
}
@@ -9812,14 +9812,19 @@
} else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
stackId = FREEFORM_WORKSPACE_STACK_ID;
}
+
+ // Reparent the task to the right stack if necessary
boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
if (stackId != task.getStackId()) {
- mStackSupervisor.moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, "resizeTask");
+ // Defer resume until the task is resized below
+ task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+ DEFER_RESUME, "resizeTask");
preserveWindow = false;
}
- task.resize(bounds, resizeMode, preserveWindow, false /* deferResume */);
+ // After reparenting (which only resizes the task to the stack bounds), resize the
+ // task to the actual bounds provided
+ task.resize(bounds, resizeMode, preserveWindow, !DEFER_RESUME);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10198,14 +10203,16 @@
throw new IllegalArgumentException(
"exitFreeformMode: No activity record matching token=" + token);
}
- final ActivityStack stack = r.getStackLocked(token);
+
+ final ActivityStack stack = r.getStack();
if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
throw new IllegalStateException(
"exitFreeformMode: You can only go fullscreen from freeform.");
}
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
- mStackSupervisor.moveTaskToStackLocked(r.task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
- ON_TOP, !FORCE_FOCUS, "exitFreeformMode", ANIMATE);
+ r.task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT,
+ ANIMATE, !DEFER_RESUME, "exitFreeformMode");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10222,15 +10229,22 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
+ return;
+ }
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ " to stackId=" + stackId + " toTop=" + toTop);
if (stackId == DOCKED_STACK_ID) {
mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
null /* initialBounds */);
}
- boolean result = mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop,
- !FORCE_FOCUS, "moveTaskToStack", ANIMATE);
- if (result && stackId == DOCKED_STACK_ID) {
+
+ final boolean successful = task.reparent(stackId, toTop,
+ REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
+ if (successful && stackId == DOCKED_STACK_ID) {
// If task moved to docked stack - show recents if needed.
mWindowManager.showRecentApps(false /* fromHome */);
}
@@ -10262,22 +10276,23 @@
// TODO: App transition
mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
- // Defer the resume so resume/pausing while moving stacks is dangerous.
- mStackSupervisor.moveTaskToStackLocked(topTask.taskId, DOCKED_STACK_ID,
- false /* toTop */, !FORCE_FOCUS, "swapDockedAndFullscreenStack",
- ANIMATE, true /* deferResume */);
+ // Defer the resume until we move all the docked tasks to the fullscreen stack below
+ topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+ DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
final int size = tasks.size();
for (int i = 0; i < size; i++) {
final int id = tasks.get(i).taskId;
if (id == topTask.taskId) {
continue;
}
- mStackSupervisor.moveTaskToStackLocked(id,
- FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */, !FORCE_FOCUS,
- "swapDockedAndFullscreenStack", ANIMATE, true /* deferResume */);
+
+ // Defer the resume until after all the tasks have been moved
+ tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+ REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
+ "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
}
- // Because we deferred the resume, to avoid conflicts with stack switches while
+ // Because we deferred the resume to avoid conflicts with stack switches while
// resuming, we need to do it after all the tasks are moved.
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -10310,12 +10325,20 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
+ return false;
+ }
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
- final boolean moved = mStackSupervisor.moveTaskToStackLocked(
- taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
- animate, DEFER_RESUME);
+
+ // Defer resuming until we move the home stack to the front below
+ final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+ REPARENT_KEEP_STACK_AT_FRONT, animate, DEFER_RESUME,
+ "moveTaskToDockedStack");
if (moved) {
if (moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
@@ -10449,7 +10472,8 @@
stack.positionChildAt(task, position);
} else {
// Reparent to new stack.
- task.reparent(stackId, position, "positionTaskInStack");
+ task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
+ !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -12087,6 +12111,12 @@
mRecentTasks.notifyTaskPersisterLocked(task, flush);
}
+ /** Notifies all listeners when the pinned stack animation starts. */
+ @Override
+ public void notifyPinnedStackAnimationStarted() {
+ mTaskChangeNotificationController.notifyPinnedStackAnimationStarted();
+ }
+
/** Notifies all listeners when the pinned stack animation ends. */
@Override
public void notifyPinnedStackAnimationEnded() {
@@ -14439,7 +14469,8 @@
boolean dumpClient = false;
boolean dumpCheckin = false;
boolean dumpCheckinFormat = false;
- boolean dumpVisibleStacks = false;
+ boolean dumpVisibleStacksOnly = false;
+ boolean dumpFocusedStackOnly = false;
String dumpPackage = null;
int opti = 0;
@@ -14454,7 +14485,9 @@
} else if ("-c".equals(opt)) {
dumpClient = true;
} else if ("-v".equals(opt)) {
- dumpVisibleStacks = true;
+ dumpVisibleStacksOnly = true;
+ } else if ("-f".equals(opt)) {
+ dumpFocusedStackOnly = true;
} else if ("-p".equals(opt)) {
if (opti < args.length) {
dumpPackage = args[opti];
@@ -14645,7 +14678,8 @@
LockGuard.dump(fd, pw, args);
} else {
// Dumping a single activity?
- if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacks)) {
+ if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll, dumpVisibleStacksOnly,
+ dumpFocusedStackOnly)) {
ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
int res = shell.exec(this, null, fd, null, args, null,
new ResultReceiver(null));
@@ -15584,13 +15618,17 @@
* - the cmd arg isn't the flattened component name of an existing activity:
* dump all activity whose component contains the cmd as a substring
* - A hex number of the ActivityRecord object instance.
+ *
+ * @param dumpVisibleStacksOnly dump activity with {@param name} only if in a visible stack
+ * @param dumpFocusedStackOnly dump activity with {@param name} only if in the focused stack
*/
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll, boolean dumpVisibleStacks) {
+ int opti, boolean dumpAll, boolean dumpVisibleStacksOnly, boolean dumpFocusedStackOnly) {
ArrayList<ActivityRecord> activities;
synchronized (this) {
- activities = mStackSupervisor.getDumpActivitiesLocked(name, dumpVisibleStacks);
+ activities = mStackSupervisor.getDumpActivitiesLocked(name, dumpVisibleStacksOnly,
+ dumpFocusedStackOnly);
}
if (activities.size() <= 0) {
@@ -18845,7 +18883,9 @@
appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,
resultExtras, ordered, sticky, false, userId);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
- final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
+ final boolean replaced = replacePending
+ && (queue.replaceParallelBroadcastLocked(r) != null);
+ // Note: We assume resultTo is null for non-ordered broadcasts.
if (!replaced) {
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
@@ -18944,8 +18984,25 @@
if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
"Enqueueing broadcast " + r.intent.getAction());
- boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
- if (!replaced) {
+ final BroadcastRecord oldRecord =
+ replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
+ if (oldRecord != null) {
+ // Replaced, fire the result-to receiver.
+ if (oldRecord.resultTo != null) {
+ final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
+ try {
+ oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
+ oldRecord.intent,
+ Activity.RESULT_CANCELED, null, null,
+ false, false, oldRecord.userId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failure ["
+ + queue.mQueueName + "] sending broadcast result of "
+ + intent, e);
+
+ }
+ }
+ } else {
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -18962,8 +19019,7 @@
return ActivityManager.BROADCAST_SUCCESS;
}
- final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
- int skipCount, long dispatchTime) {
+ final void rotateBroadcastStatsIfNeededLocked() {
final long now = SystemClock.elapsedRealtime();
if (mCurBroadcastStats == null ||
(mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
@@ -18974,9 +19030,19 @@
}
mCurBroadcastStats = new BroadcastStats();
}
+ }
+
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ rotateBroadcastStatsIfNeededLocked();
mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
}
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+ }
+
final Intent verifyBroadcastLocked(Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 10055c8..c79ed2a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -797,6 +797,15 @@
+ " is already the parent of r=" + this);
}
+ // TODO: Ensure that we do not directly reparent activities across stacks, as that may leave
+ // the stacks in strange states. For now, we should use Task.reparent() to ensure that
+ // the stack is left in an OK state.
+ if (prevTask != null && newTask != null && prevTask.getStack() != newTask.getStack()) {
+ throw new IllegalArgumentException(reason + ": task=" + newTask
+ + " is in a different stack (" + newTask.getStackId() + ") than the parent of"
+ + " r=" + this + " (" + prevTask.getStackId() + ")");
+ }
+
// Must reparent first in window manager
mWindowContainerController.reparent(newTask.getWindowContainerController(), position);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7f7caff..5d4bff9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3827,7 +3827,9 @@
r.app = null;
r.removeWindowContainer();
final TaskRecord task = r.task;
- if (task != null && task.removeActivity(r)) {
+ final boolean lastActivity = task != null ? task.removeActivity(r) : false;
+
+ if (lastActivity) {
if (DEBUG_STACK) Slog.i(TAG_STACK,
"removeActivityFromHistoryLocked: last activity removed from " + this);
if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
@@ -4819,7 +4821,7 @@
}
ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
- ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+ ArrayList<ActivityRecord> activities = new ArrayList<>();
if ("all".equals(name)) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -5075,45 +5077,6 @@
moveToFront(reason);
}
- /**
- * Moves the input activity from its current stack to this one.
- * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is
- * created on this stack which the activity is added to.
- * */
- void moveActivityToStack(ActivityRecord r) {
- final ActivityStack prevStack = r.getStack();
- if (prevStack.mStackId == mStackId) {
- // You are already in the right stack silly...
- return;
- }
-
- final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
- && (mStackSupervisor.topRunningActivityLocked() == r);
- final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
- final boolean wasPaused = prevStack.mPausingActivity == r;
-
- // Create a new task for the activity to be parented in
- final TaskRecord task = createTaskRecord(
- mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
- r.info, r.intent, null, null, true, r.mActivityType);
- // This is a new task, so reparenting it to position 0 will move it to the top
- r.reparent(task, 0 /* position */, "moveActivityToStack");
-
- // Notify the task actiivties if it was moved to/from a pinned stack
- mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
-
- // Resume the activity if necessary after it has moved
- moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
- "moveActivityToStack");
- if (wasResumed) {
- prevStack.mResumedActivity = null;
- }
- if (wasPaused) {
- prevStack.mPausingActivity = null;
- prevStack.removeTimeoutsForActivityLocked(r);
- }
- }
-
public int getStackId() {
return mStackId;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index ab1559d..6e138a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -89,6 +89,9 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static java.lang.Integer.MAX_VALUE;
@@ -1992,8 +1995,8 @@
stackId = task.getLaunchStackId();
}
if (stackId != currentStack.mStackId) {
- currentStack = moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, reason);
+ task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "findTaskToMoveToFrontLocked");
stackId = currentStack.mStackId;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
@@ -2278,6 +2281,7 @@
if (onTop) {
for (int i = 0; i < size; i++) {
final TaskRecord task = tasks.get(i);
+ final boolean isTopTask = i == (size - 1);
if (fromStackId == PINNED_STACK_ID) {
// Update the return-to to reflect where the pinned stack task was moved
// from so that we retain the stack that was previously visible if the
@@ -2287,22 +2291,25 @@
MetricsLogger.action(mService.mContext,
MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
}
- moveTaskToStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, onTop, onTop /*forceFocus*/,
- "moveTasksToFullscreenStack - onTop", ANIMATE, DEFER_RESUME);
+ // Defer resume until all the tasks have been moved to the fullscreen stack
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+ REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
+ "moveTasksToFullscreenStack - onTop");
}
-
- ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
- resumeFocusedStackTopActivityLocked();
} else {
for (int i = 0; i < size; i++) {
final TaskRecord task = tasks.get(i);
- final int position = fullscreenStack != null ?
- Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+ final int position = fullscreenStack != null
+ ? Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+ // Defer resume until all the tasks have been moved to the fullscreen stack
task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, position,
- "moveTasksToFullscreenStack - NOT_onTop");
+ REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+ DEFER_RESUME, "moveTasksToFullscreenStack - NOT_onTop");
}
}
+
+ ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+ resumeFocusedStackTopActivityLocked();
} finally {
mAllowDockedStackResize = true;
mWindowManager.continueSurfaceLayout();
@@ -2445,14 +2452,16 @@
? Math.max(0, fullscreenStack.getChildCount() - 1)
: fullscreenStack.getChildCount();
final TaskRecord task = tasks.get(i);
- task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition, "removeStack");
+ // Defer resume until we remove all the tasks
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition,
+ REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, "removeStack");
}
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
} else {
// If there is no fullscreen stack, then create the stack and move all the tasks
// onto the stack
- moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */);
+ moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
}
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -2657,168 +2666,52 @@
}
/**
- * Moves the specified task record to the input stack id.
- * WARNING: This method performs an unchecked/raw move of the task and
- * can leave the system in an unstable state if used incorrectly.
- * Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
- * @param task Task to move.
- * @param stackId Id of stack to move task to.
- * @param toTop True if the task should be placed at the top of the stack.
- * @param forceFocus if focus should be moved to the new stack
- * @param reason Reason the task is been moved.
- * @return The stack the task was moved to.
+ * Returns the reparent target stack, creating the stack if necessary. This call also enforces
+ * the various checks on tasks that are going to be reparented from one stack to another.
*/
- ActivityStack moveTaskToStackUncheckedLocked(TaskRecord task, int stackId, boolean toTop,
- boolean forceFocus, String reason) {
-
- if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
- throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
- + "support multi-window task=" + task + " to stackId=" + stackId);
- }
-
- final ActivityRecord r = task.topRunningActivityLocked();
+ ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
final ActivityStack prevStack = task.getStack();
- final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
- final boolean wasResumed = prevStack.mResumedActivity == r;
- final boolean wasPaused = prevStack.mPausingActivity == r;
- // In some cases the focused stack isn't the front stack. E.g. pinned stack.
- // Whenever we are moving the top activity from the front stack we want to make sure to move
- // the stack to the front.
- final boolean wasFront = isFrontStackOnDisplay(prevStack)
- && (prevStack.topRunningActivityLocked() == r);
- if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
- // We don't allow moving a unresizeable task to the docked stack since the docked
- // stack is used for split-screen mode and will cause things like the docked divider to
- // show up. We instead leave the task in its current stack or move it to the fullscreen
- // stack if it isn't currently in a stack.
- stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
- Slog.w(TAG, "Can not move unresizeable task=" + task
- + " to docked stack. Moving to stackId=" + stackId + " instead.");
+ // Check that we aren't reparenting to the same stack that the task is already in
+ if (prevStack != null && prevStack.mStackId == stackId) {
+ Slog.w(TAG, "Can not reparent to same stack, task=" + task
+ + " already in stackId=" + stackId);
+ return prevStack;
}
- // Temporarily disable resizeablility of task we are moving. We don't want it to be resized
- // if a docked stack is created below which will lead to the stack we are moving from and
- // its resizeable tasks being resized.
- task.mTemporarilyUnresizable = true;
- final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
- task.mTemporarilyUnresizable = false;
- task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
-
- // Reset the resumed activity on the previous stack
- if (wasResumed) {
- prevStack.mResumedActivity = null;
- }
- // Reset the paused activity on the previous stack
- if (wasPaused) {
- prevStack.mPausingActivity = null;
- prevStack.removeTimeoutsForActivityLocked(r);
+ // Ensure that we aren't trying to move into a multi-window stack without multi-window
+ // support
+ if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+ throw new IllegalArgumentException("Device doesn't support multi-window, can not"
+ + " reparent task=" + task + " to stackId=" + stackId);
}
- // If the task had focus before (or we're requested to move focus),
- // move focus to the new stack by moving the stack to the front.
- stack.moveToFrontAndResumeStateIfNeeded(r, forceFocus || wasFocused || wasFront, wasResumed,
- wasPaused, reason);
-
- return stack;
- }
-
- boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason, boolean animate) {
- return moveTaskToStackLocked(taskId, stackId, toTop, forceFocus, reason, animate,
- false /* deferResume */);
- }
-
- boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason, boolean animate, boolean deferResume) {
- final TaskRecord task = anyTaskForIdLocked(taskId);
- if (task == null) {
- Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
- return false;
- }
-
- final ActivityStack currentStack = task.getStack();
- if (currentStack != null && currentStack.mStackId == stackId) {
- // You are already in the right stack silly...
- Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
- return true;
- }
-
+ // Ensure that we aren't trying to move into a freeform stack without freeform
+ // support
if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
- throw new IllegalArgumentException("moveTaskToStack:"
- + "Attempt to move task " + taskId + " to unsupported freeform stack");
+ throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
+ + " task=" + task);
}
- final ActivityRecord topActivity = task.getTopActivity();
- final int sourceStackId = task.getStackId();
- final boolean mightReplaceWindow =
- StackId.replaceWindowsOnTaskMove(sourceStackId, stackId) && topActivity != null;
- if (mightReplaceWindow) {
- // We are about to relaunch the activity because its configuration changed due to
- // being maximized, i.e. size change. The activity will first remove the old window
- // and then add a new one. This call will tell window manager about this, so it can
- // preserve the old window until the new one is drawn. This prevents having a gap
- // between the removal and addition, in which no window is visible. We also want the
- // entrance of the new window to be properly animated.
- // Note here we always set the replacing window first, as the flags might be needed
- // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
- mWindowManager.setWillReplaceWindow(topActivity.appToken, animate);
+ // We don't allow moving a unresizeable task to the docked stack since the docked stack is
+ // used for split-screen mode and will cause things like the docked divider to show up. We
+ // instead leave the task in its current stack or move it to the fullscreen stack if it
+ // isn't currently in a stack.
+ if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
+ stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+ Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
+ + " Moving to stackId=" + stackId + " instead.");
}
- mWindowManager.deferSurfaceLayout();
- final int preferredLaunchStackId = stackId;
- boolean kept = true;
+ // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
+ // example, a docked stack is created which will lead to the stack we are moving from being
+ // resized and and its resizeable tasks being resized.
try {
- final ActivityStack stack = moveTaskToStackUncheckedLocked(
- task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
- stackId = stack.mStackId;
-
- if (!animate) {
- stack.mNoAnimActivities.add(topActivity);
- }
-
- // We might trigger a configuration change. Save the current task bounds for freezing.
- // TODO: Should this call be moved inside the resize method in WM?
- stack.prepareFreezingTaskBounds();
-
- // Make sure the task has the appropriate bounds/size for the stack it is in.
- if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
- && !Objects.equals(task.mBounds, stack.mBounds)) {
- kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
- deferResume);
- } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
- Rect bounds = task.getLaunchBounds();
- if (bounds == null) {
- stack.layoutTaskInStack(task, null);
- bounds = task.mBounds;
- }
- kept = task.resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
- } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
- kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
- deferResume);
- }
+ task.mTemporarilyUnresizable = true;
+ return getStack(stackId, CREATE_IF_NEEDED, toTop);
} finally {
- mWindowManager.continueSurfaceLayout();
+ task.mTemporarilyUnresizable = false;
}
-
- if (mightReplaceWindow) {
- // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
- // window), we need to clear the replace window settings. Otherwise, we schedule a
- // timeout to remove the old window if the replacing window is not coming in time.
- mWindowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
- }
-
- if (!deferResume) {
-
- // The task might have already been running and its visibility needs to be synchronized with
- // the visibility of the stack / windows.
- ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
- resumeFocusedStackTopActivityLocked();
- }
-
- handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);
-
- return (preferredLaunchStackId == stackId);
}
boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
@@ -2849,8 +2742,8 @@
void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
boolean moveHomeStackToFront) {
-
mWindowManager.deferSurfaceLayout();
+
// Need to make sure the pinned stack exist so we can resize it below...
final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
@@ -2882,12 +2775,26 @@
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
}
- moveTaskToStackLocked(
- task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+ // Defer resume until below
+ task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ DEFER_RESUME, reason);
} else {
// There are multiple activities in the task and moving the top activity should
- // reveal/leave the other activities in their original task
- stack.moveActivityToStack(r);
+ // reveal/leave the other activities in their original task.
+
+ // Currently, we don't support reparenting activities across tasks in two different
+ // stacks, so instead, just create a new task in the same stack, reparent the
+ // activity into that task, and then reparent the whole task to the new stack. This
+ // ensures that all the necessary work to migrate states in the old and new stacks
+ // is also done.
+ final TaskRecord newTask = task.getStack().createTaskRecord(
+ getNextTaskIdForUserLocked(r.userId), r.info, r.intent, null, null, true,
+ r.mActivityType);
+ r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
+
+ // Defer resume until below
+ newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ DEFER_RESUME, reason);
}
// Reset the state that indicates it can enter PiP while pausing after we've moved it
@@ -3521,22 +3428,24 @@
* Dumps the activities matching the given {@param name} in the either the focused stack
* or all visible stacks if {@param dumpVisibleStacks} is true.
*/
- ArrayList<ActivityRecord> getDumpActivitiesLocked(String name, boolean dumpVisibleStacks) {
- if (dumpVisibleStacks) {
+ ArrayList<ActivityRecord> getDumpActivitiesLocked(String name, boolean dumpVisibleStacksOnly,
+ boolean dumpFocusedStackOnly) {
+ if (dumpFocusedStackOnly) {
+ return mFocusedStack.getDumpActivitiesLocked(name);
+ } else {
ArrayList<ActivityRecord> activities = new ArrayList<>();
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
ActivityStack stack = stacks.get(stackNdx);
- if (stack.getStackVisibilityLocked(null) == STACK_VISIBLE) {
+ if (!dumpVisibleStacksOnly ||
+ stack.getStackVisibilityLocked(null) == STACK_VISIBLE) {
activities.addAll(stack.getDumpActivitiesLocked(name));
}
}
}
return activities;
- } else {
- return mFocusedStack.getDumpActivitiesLocked(name);
}
}
@@ -4878,9 +4787,8 @@
if (launchStackId != INVALID_STACK_ID) {
if (task.getStackId() != launchStackId) {
- moveTaskToStackLocked(
- taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
- ANIMATE);
+ task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
+ DEFER_RESUME, "startActivityFromRecents");
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index d8c5533..b9ae4fd 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -77,11 +77,16 @@
import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
+import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
import static com.android.server.am.EventLogTags.AM_NEW_INTENT;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+
+import static java.lang.Integer.MAX_VALUE;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -100,7 +105,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -509,22 +513,23 @@
doPendingActivityLaunchesLocked(false);
return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
- options, inTask);
+ options, inTask);
}
- /** Creates a launch intent for the given auxiliary resolution data. */
+ /**
+ * Creates a launch intent for the given auxiliary resolution data.
+ */
private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse,
- Intent originalIntent, String callingPackage,
- String resolvedType, int userId) {
+ Intent originalIntent, String callingPackage, String resolvedType, int userId) {
if (auxiliaryResponse.needsPhaseTwo) {
// request phase two resolution
mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
- auxiliaryResponse, originalIntent, resolvedType, callingPackage, userId);
+ auxiliaryResponse, originalIntent, resolvedType, callingPackage, userId);
}
return EphemeralResolver.buildEphemeralInstallerIntent(originalIntent,
- callingPackage, resolvedType, userId, auxiliaryResponse.packageName,
- auxiliaryResponse.splitName, auxiliaryResponse.versionCode,
- auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
+ callingPackage, resolvedType, userId, auxiliaryResponse.packageName,
+ auxiliaryResponse.splitName, auxiliaryResponse.versionCode,
+ auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
}
void postStartActivityProcessing(
@@ -930,14 +935,13 @@
}
private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
- IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
- int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
int result = START_CANCELED;
try {
mService.mWindowManager.deferSurfaceLayout();
result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
- startFlags,
- doResume, options, inTask);
+ startFlags, doResume, options, inTask);
} finally {
// If we are not able to proceed, disassociate the activity from the task. Leaving an
// activity in an incomplete state can lead to issues, such as performing operations
@@ -948,8 +952,8 @@
mService.mWindowManager.continueSurfaceLayout();
}
- postStartActivityProcessing(r, result, mSupervisor.mFocusedStack.mStackId,
- mSourceRecord, mTargetStack);
+ postStartActivityProcessing(r, result, mSupervisor.mFocusedStack.mStackId, mSourceRecord,
+ mTargetStack);
return result;
}
@@ -1000,11 +1004,22 @@
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| mLaunchSingleInstance || mLaunchSingleTask) {
+ final TaskRecord task = mReusedActivity.task;
+
// In this situation we want to remove all activities from the task up to the one
// being started. In most cases this means we are resetting the task to its initial
// state.
- final ActivityRecord top = mReusedActivity.task.performClearTaskForReuseLocked(
- mStartActivity, mLaunchFlags);
+ final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
+ mLaunchFlags);
+
+ // The above code can remove {@code mReusedActivity} from the task, leading to the
+ // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
+ // task reference is needed in the call below to
+ // {@link setTargetStackAndMoveToFrontIfNeeded}.
+ if (mReusedActivity.task == null) {
+ mReusedActivity.task = task;
+ }
+
if (top != null) {
if (top.frontOfTask) {
// Activity aliases may mean we use different intents for the top activity,
@@ -1479,9 +1494,9 @@
if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
// If we want to launch adjacent and mTargetStack is not the computed
// launch stack - move task to top of computed stack.
- mSupervisor.moveTaskToStackLocked(intentActivity.task.taskId,
- launchStack.mStackId, ON_TOP, FORCE_FOCUS, "launchToSide",
- ANIMATE);
+ intentActivity.task.reparent(launchStack.mStackId, ON_TOP,
+ REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+ "launchToSide");
} else {
// TODO: This should be reevaluated in MW v2.
// We choose to move task to front instead of launching it adjacent
@@ -1546,9 +1561,13 @@
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
// The caller has requested to completely replace any existing task with its new
// activity. Well that should not be too hard...
- intentActivity.task.performClearTaskLocked();
- intentActivity.task.setIntent(mStartActivity);
+ // Note: we must persist the {@link TaskRecord} first as intentActivity could be
+ // removed from calling performClearTaskLocked (For example, if it is being brought out
+ // of history or if it is finished immediately), thus disassociating the task.
mReuseTask = intentActivity.task;
+ mReuseTask.performClearTaskLocked();
+ mReuseTask.setIntent(mStartActivity);
+
// When we clear the task - focus will be adjusted, which will bring another task
// to top before we launch the activity we need. This will temporary swap their
// mTaskToReturnTo values and we don't want to overwrite them accidentally.
@@ -1562,6 +1581,9 @@
// running, and the caller has asked to clear the current task to have this
// activity at the top.
mAddingToTask = true;
+
+ // We are no longer placing the activity in the task we previously thought we were.
+ mStartActivity.task = null;
// Now pretend like this activity is being started by the top of its task, so it
// is put in the right place.
mSourceRecord = intentActivity;
@@ -1693,8 +1715,8 @@
if (mTargetStack == null) {
mTargetStack = sourceStack;
} else if (mTargetStack != sourceStack) {
- mSupervisor.moveTaskToStackLocked(sourceTask.taskId, mTargetStack.mStackId,
- ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE);
+ sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+ !ANIMATE, DEFER_RESUME, "launchToSide");
}
final TaskRecord topTask = mTargetStack.topTask();
@@ -1760,9 +1782,9 @@
mInTask.updateOverrideConfiguration(mLaunchBounds);
int stackId = mInTask.getLaunchStackId();
if (stackId != mInTask.getStackId()) {
- final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(mInTask,
- stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
- stackId = stack.mStackId;
+ mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "inTaskToFront");
+ stackId = mInTask.getStackId();
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
@@ -1862,7 +1884,7 @@
final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
try {
startActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags, resume, null,
- null);
+ null);
} catch (Exception e) {
Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
pal.sendErrorResult(e.getMessage());
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 68253c78..c9d19cb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -239,33 +239,37 @@
}
}
- public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
- final Intent intent = r.intent;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- final Intent curIntent = mParallelBroadcasts.get(i).intent;
- if (intent.filterEquals(curIntent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "***** DROPPING PARALLEL ["
- + mQueueName + "]: " + intent);
- mParallelBroadcasts.set(i, r);
- return true;
- }
- }
- return false;
+ /**
+ * Find the same intent from queued parallel broadcast, replace with a new one and return
+ * the old one.
+ */
+ public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
+ return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
}
- public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) {
+ /**
+ * Find the same intent from queued ordered broadcast, replace with a new one and return
+ * the old one.
+ */
+ public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
+ return replaceBroadcastLocked(mOrderedBroadcasts, r, "ORDERED");
+ }
+
+ private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
+ BroadcastRecord r, String typeForLogging) {
final Intent intent = r.intent;
- for (int i = mOrderedBroadcasts.size() - 1; i > 0; i--) {
- if (intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "***** DROPPING ORDERED ["
- + mQueueName + "]: " + intent);
- mOrderedBroadcasts.set(i, r);
- return true;
+ for (int i = queue.size() - 1; i > 0; i--) {
+ final BroadcastRecord old = queue.get(i);
+ if (old.userId == r.userId && intent.filterEquals(old.intent)) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG_BROADCAST, "***** DROPPING "
+ + typeForLogging + " [" + mQueueName + "]: " + intent);
+ }
+ queue.set(i, r);
+ return old;
}
}
- return false;
+ return null;
}
private final void processCurBroadcastLocked(BroadcastRecord r,
@@ -1191,6 +1195,8 @@
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0))) {
+ mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
+ component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
diff --git a/services/core/java/com/android/server/am/BroadcastStats.java b/services/core/java/com/android/server/am/BroadcastStats.java
index fdbaada..fd24582 100644
--- a/services/core/java/com/android/server/am/BroadcastStats.java
+++ b/services/core/java/com/android/server/am/BroadcastStats.java
@@ -47,6 +47,7 @@
static final class ActionEntry {
final String mAction;
final ArrayMap<String, PackageEntry> mPackages = new ArrayMap<>();
+ final ArrayMap<String, ViolationEntry> mBackgroundCheckViolations = new ArrayMap<>();
int mReceiveCount;
int mSkipCount;
long mTotalDispatchTime;
@@ -61,6 +62,10 @@
int mSendCount;
}
+ static final class ViolationEntry {
+ int mCount;
+ }
+
public BroadcastStats() {
mStartRealtime = SystemClock.elapsedRealtime();
mStartUptime = SystemClock.uptimeMillis();
@@ -87,6 +92,20 @@
pe.mSendCount++;
}
+ public void addBackgroundCheckViolation(String action, String targetPackage) {
+ ActionEntry ae = mActions.get(action);
+ if (ae == null) {
+ ae = new ActionEntry(action);
+ mActions.put(action, ae);
+ }
+ ViolationEntry ve = ae.mBackgroundCheckViolations.get(targetPackage);
+ if (ve == null) {
+ ve = new ViolationEntry();
+ ae.mBackgroundCheckViolations.put(targetPackage, ve);
+ }
+ ve.mCount++;
+ }
+
public boolean dumpStats(PrintWriter pw, String prefix, String dumpPackage) {
boolean printedSomething = false;
ArrayList<ActionEntry> actions = new ArrayList<>(mActions.size());
@@ -123,6 +142,15 @@
pw.print(pe.mSendCount);
pw.println(" times");
}
+ for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+ pw.print(prefix);
+ pw.print(" Bg Check Violation ");
+ pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+ pw.print(": ");
+ ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+ pw.print(ve.mCount);
+ pw.println(" times");
+ }
}
return printedSomething;
}
@@ -158,6 +186,14 @@
pw.print(pe.mSendCount);
pw.println();
}
+ for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+ pw.print("v,");
+ pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+ ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+ pw.print(",");
+ pw.print(ve.mCount);
+ pw.println();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 9dfc7cd..9a98bc6 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -46,6 +46,7 @@
static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
+ static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -100,6 +101,10 @@
l.onPinnedActivityRestartAttempt((String) m.obj);
};
+ private final TaskStackConsumer mNotifyPinnedStackAnimationStarted = (l, m) -> {
+ l.onPinnedStackAnimationStarted();
+ };
+
private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
l.onPinnedStackAnimationEnded();
};
@@ -166,6 +171,9 @@
case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
break;
+ case NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyPinnedStackAnimationStarted, msg);
+ break;
case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedStackAnimationEnded, msg);
break;
@@ -276,6 +284,15 @@
msg.sendToTarget();
}
+ /** Notifies all listeners when the pinned stack animation starts. */
+ void notifyPinnedStackAnimationStarted() {
+ mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+ final Message msg =
+ mHandler.obtainMessage(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+ forAllLocalListeners(mNotifyPinnedStackAnimationStarted, msg);
+ msg.sendToTarget();
+ }
+
/** Notifies all listeners when the pinned stack animation ends. */
void notifyPinnedStackAnimationEnded() {
mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index b051304..3f33f41 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -54,6 +55,7 @@
import com.android.server.wm.StackWindowController;
import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.TaskWindowContainerListener;
+import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,10 +64,13 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
import static android.app.ActivityManager.RESIZE_MODE_FORCED;
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -107,9 +112,10 @@
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static java.lang.Integer.MAX_VALUE;
+
final class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -160,6 +166,24 @@
static final int INVALID_TASK_ID = -1;
private static final int INVALID_MIN_SIZE = -1;
+ /**
+ * The modes to control how the stack is moved to the front when calling
+ * {@link TaskRecord#reparent}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REPARENT_MOVE_STACK_TO_FRONT,
+ REPARENT_KEEP_STACK_AT_FRONT,
+ REPARENT_LEAVE_STACK_IN_PLACE
+ })
+ public @interface ReparentMoveStackMode {}
+ // Moves the stack to the front if it was not at the front
+ public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+ // Only moves the stack to the front if it was focused or front most already
+ public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+ // Do not move the stack as a part of reparenting
+ public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
@@ -537,36 +561,159 @@
mWindowContainerController.getBounds(bounds);
}
- // TODO: Should we be doing all the stuff in ASS.moveTaskToStackLocked?
- void reparent(int stackId, int position, String reason) {
- mService.mWindowManager.deferSurfaceLayout();
+ /**
+ * Convenience method to reparent a task to the top or bottom position of the stack.
+ */
+ boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
+ boolean animate, boolean deferResume, String reason) {
+ return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+ deferResume, reason);
+ }
+ /**
+ * Reparents the task into a preferred stack, creating it if necessary.
+ *
+ * @param preferredStackId the stack id of the target stack to move this task
+ * @param position the position to place this task in the new stack
+ * @param animate whether or not we should wait for the new window created as a part of the
+ * reparenting to be drawn and animated in
+ * @param moveStackMode whether or not to move the stack to the front always, only if it was
+ * previously focused & in front, or never
+ * @param deferResume whether or not to update the visibility of other tasks and stacks that may
+ * have changed as a result of this reparenting
+ * @param reason the caller of this reparenting
+ * @return
+ */
+ boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
+ boolean animate, boolean deferResume, String reason) {
+ final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+ final WindowManagerService windowManager = mService.mWindowManager;
+ final ActivityStack sourceStack = getStack();
+ final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+ position == MAX_VALUE);
+ if (toStack == sourceStack) {
+ return false;
+ }
+
+ final int sourceStackId = getStackId();
+ final int stackId = toStack.getStackId();
+ final ActivityRecord topActivity = getTopActivity();
+
+ final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
+ && topActivity != null;
+ if (mightReplaceWindow) {
+ // We are about to relaunch the activity because its configuration changed due to
+ // being maximized, i.e. size change. The activity will first remove the old window
+ // and then add a new one. This call will tell window manager about this, so it can
+ // preserve the old window until the new one is drawn. This prevents having a gap
+ // between the removal and addition, in which no window is visible. We also want the
+ // entrance of the new window to be properly animated.
+ // Note here we always set the replacing window first, as the flags might be needed
+ // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
+ windowManager.setWillReplaceWindow(topActivity.appToken, animate);
+ }
+
+ windowManager.deferSurfaceLayout();
+ boolean kept = true;
try {
- final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
- final ActivityStack newStack = supervisor.getStack(stackId,
- CREATE_IF_NEEDED, false /* toTop */);
+ final ActivityRecord r = topRunningActivityLocked();
+ final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+ && (topRunningActivityLocked() == r);
+ final boolean wasResumed = sourceStack.mResumedActivity == r;
+ final boolean wasPaused = sourceStack.mPausingActivity == r;
+
+ // In some cases the focused stack isn't the front stack. E.g. pinned stack.
+ // Whenever we are moving the top activity from the front stack we want to make sure to
+ // move the stack to the front.
+ final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+ && (sourceStack.topRunningActivityLocked() == r);
+
// Adjust the position for the new parent stack as needed.
- position = newStack.getAdjustedPositionForTask(this, position, null /* starting */);
+ position = toStack.getAdjustedPositionForTask(this, position, null /* starting */);
// Must reparent first in window manager to avoid a situation where AM can delete the
// we are coming from in WM before we reparent because it became empty.
- mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
+ mWindowContainerController.reparent(toStack.getWindowContainerController(), position);
- final ActivityStack prevStack = mStack;
- prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
- newStack.addTask(this, position, reason);
+ // Reset the resumed activity on the previous stack
+ if (wasResumed) {
+ sourceStack.mResumedActivity = null;
+ }
- supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, prevStack);
+ // Reset the paused activity on the previous stack
+ if (wasPaused) {
+ sourceStack.mPausingActivity = null;
+ sourceStack.removeTimeoutsForActivityLocked(r);
+ }
+ // Move the task
+ sourceStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
+ toStack.addTask(this, position, reason);
+
+ // TODO: Ensure that this is actually necessary here
+ // Notify of picture-in-picture mode changes
+ supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, sourceStack);
+
+ // TODO: Ensure that this is actually necessary here
+ // Notify the voice session if required
if (voiceSession != null) {
try {
voiceSession.taskStarted(intent, taskId);
} catch (RemoteException e) {
}
}
+
+ // If the task had focus before (or we're requested to move focus), move focus to the
+ // new stack by moving the stack to the front.
+ final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
+ || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
+ toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
+ reason);
+ if (!animate) {
+ toStack.mNoAnimActivities.add(topActivity);
+ }
+
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ // TODO: Should this call be moved inside the resize method in WM?
+ toStack.prepareFreezingTaskBounds();
+
+ // Make sure the task has the appropriate bounds/size for the stack it is in.
+ if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ && !Objects.equals(mBounds, toStack.mBounds)) {
+ kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ deferResume);
+ } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ Rect bounds = getLaunchBounds();
+ if (bounds == null) {
+ toStack.layoutTaskInStack(this, null);
+ bounds = mBounds;
+ }
+ kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
+ } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
+ kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ deferResume);
+ }
} finally {
- mService.mWindowManager.continueSurfaceLayout();
+ windowManager.continueSurfaceLayout();
}
+
+ if (mightReplaceWindow) {
+ // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
+ // window), we need to clear the replace window settings. Otherwise, we schedule a
+ // timeout to remove the old window if the replacing window is not coming in time.
+ windowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
+ }
+
+ if (!deferResume) {
+ // The task might have already been running and its visibility needs to be synchronized
+ // with the visibility of the stack / windows.
+ supervisor.ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
+ supervisor.resumeFocusedStackTopActivityLocked();
+ }
+
+ supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, stackId);
+
+ return (preferredStackId == stackId);
}
void cancelWindowTransition() {
@@ -1081,11 +1228,13 @@
r.onOverrideConfigurationSent();
}
- /** @return true if this was the last activity in the task */
+ /**
+ * @return true if this was the last activity in the task
+ */
boolean removeActivity(ActivityRecord r) {
if (r.task != this) {
throw new IllegalArgumentException(
- "Activity=" + r + " does not belong to task=" + this);
+ "Activity=" + r + " does not belong to task=" + this);
}
r.task = null;
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 42c31b1..6710bdc 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -80,10 +79,11 @@
}
public void setState(int newState) {
- if (DEBUG_MU) {
- Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from "
- + stateToString(state) + " to " + stateToString(newState));
+ if (newState == state) {
+ return;
}
+ Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from "
+ + stateToString(state) + " to " + stateToString(newState));
lastState = state;
state = newState;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dae5da3..0e767da 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3050,6 +3050,10 @@
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
+
+ // The system can post notifications for any package, let us resolve that.
+ final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
+
// Fix the notification as best we can.
try {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
@@ -3069,13 +3073,13 @@
channelId = (new Notification.TvExtender(notification)).getChannel();
}
final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
- callingUid, channelId, false /* includeDeleted */);
+ notificationUid, channelId, false /* includeDeleted */);
final StatusBarNotification n = new StatusBarNotification(
- pkg, opPkg, id, tag, callingUid, callingPid, notification,
+ pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
- if (!checkDisqualifyingFeatures(userId, callingUid, id,tag, r)) {
+ if (!checkDisqualifyingFeatures(userId, notificationUid, id,tag, r)) {
return;
}
@@ -3101,6 +3105,19 @@
idOut[0] = id;
}
+ private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
+ // The system can post notifications on behalf of any package it wants
+ if (isCallerSystem() && opPackageName != null && !"android".equals(opPackageName)) {
+ try {
+ return getContext().getPackageManager()
+ .getPackageUidAsUser(opPackageName, userId);
+ } catch (NameNotFoundException e) {
+ /* ignore */
+ }
+ }
+ return callingUid;
+ }
+
/**
* Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking.
*
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 867af9a..476eb10 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -66,6 +66,7 @@
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
+ private static final String ATT_NAME_RES_ID = "name_res_id";
private static final String ATT_UID = "uid";
private static final String ATT_ID = "id";
private static final String ATT_PRIORITY = "priority";
@@ -191,9 +192,14 @@
if (TAG_GROUP.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+ int groupNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
if (!TextUtils.isEmpty(id)) {
- final NotificationChannelGroup group =
- new NotificationChannelGroup(id, groupName);
+ NotificationChannelGroup group = null;
+ if (groupName != null) {
+ group = new NotificationChannelGroup(id, groupName);
+ } else {
+ group = new NotificationChannelGroup(id, groupNameRes);
+ }
r.groups.put(id, group);
}
}
@@ -201,12 +207,19 @@
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
+ int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
int channelImportance =
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
if (!TextUtils.isEmpty(id)) {
- final NotificationChannel channel = new NotificationChannel(id,
- channelName, channelImportance);
+ NotificationChannel channel;
+ if (channelName != null) {
+ channel = new NotificationChannel(id, channelName,
+ channelImportance);
+ } else {
+ channel = new NotificationChannel(id, channelNameRes,
+ channelImportance);
+ }
channel.populateFromXml(parser);
r.channels.put(id, channel);
}
@@ -286,7 +299,7 @@
NotificationChannel channel;
channel = new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID,
- mContext.getString(R.string.default_notification_channel_label),
+ R.string.default_notification_channel_label,
r.importance);
channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
channel.setLockscreenVisibility(r.visibility);
@@ -465,7 +478,8 @@
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(group.getId());
- Preconditions.checkNotNull(group.getName());
+ Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())
+ || group.getNameResId() != 0);
Record r = getOrCreateRecord(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
@@ -480,7 +494,8 @@
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(channel);
Preconditions.checkNotNull(channel.getId());
- Preconditions.checkNotNull(channel.getName());
+ Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())
+ || channel.getNameResId() != 0);
Record r = getOrCreateRecord(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index bbd227e..aac04da 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -314,7 +314,8 @@
optimizer.performDexOpt(pkg, libraryDependencies,
null /* ISAs */, false /* checkProfiles */,
getCompilerFilterForReason(compilationReason),
- null /* CompilerStats.PackageStats */);
+ null /* CompilerStats.PackageStats */,
+ mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName));
return commands;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index b589057..d9ea728 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -104,7 +104,7 @@
*/
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
- CompilerStats.PackageStats packageStats) {
+ CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps) {
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
@@ -119,7 +119,7 @@
}
try {
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
- targetCompilationFilter, packageStats);
+ targetCompilationFilter, packageStats, isUsedByOtherApps);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -135,7 +135,8 @@
@GuardedBy("mInstallLock")
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
String[] targetInstructionSets, boolean checkForProfileUpdates,
- String targetCompilerFilter, CompilerStats.PackageStats packageStats) {
+ String targetCompilerFilter, CompilerStats.PackageStats packageStats,
+ boolean isUsedByOtherApps) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
@@ -143,7 +144,7 @@
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
- targetCompilerFilter, isUsedByOtherApps(pkg));
+ targetCompilerFilter, isUsedByOtherApps);
final boolean profileUpdated = checkForProfileUpdates &&
isProfileUpdated(pkg, sharedGid, compilerFilter);
@@ -477,40 +478,6 @@
mSystemReady = true;
}
- /**
- * Returns true if the profiling data collected for the given app indicate
- * that the apps's APK has been loaded by another app.
- * Note that this returns false for all forward-locked apps and apps without
- * any collected profiling data.
- */
- public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
- if (pkg.isForwardLocked()) {
- // Skip the check for forward locked packages since they don't share their code.
- return false;
- }
-
- for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
- try {
- apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
- } catch (IOException e) {
- // Log an error but continue without it.
- Slog.w(TAG, "Failed to get canonical path", e);
- continue;
- }
- String useMarker = apkPath.replace('/', '@');
- final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
- for (int i = 0; i < currentUserIds.length; i++) {
- File profileDir =
- Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
- File foreignUseMark = new File(profileDir, useMarker);
- if (foreignUseMark.exists()) {
- return true;
- }
- }
- }
- return false;
- }
-
private String printDexoptFlags(int flags) {
ArrayList<String> flagsList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8380983..a4d35a1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -746,6 +746,12 @@
@GuardedBy("mPackages")
final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+ final PackageParser.Callback mPackageParserCallback = new PackageParser.Callback() {
+ @Override public boolean hasFeature(String feature) {
+ return PackageManagerService.this.hasSystemFeature(feature, 0);
+ }
+ };
+
public static final class SharedLibraryEntry {
public final String path;
public final String apk;
@@ -7584,7 +7590,7 @@
+ " flags=0x" + Integer.toHexString(parseFlags));
}
ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
- mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir);
+ mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, mPackageParserCallback);
// Submit files for parsing in parallel
int fileCount = 0;
@@ -7753,6 +7759,7 @@
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
+ pp.setCallback(mPackageParserCallback);
if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
@@ -8434,11 +8441,13 @@
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
false /* checkProfiles */,
getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
- getOrCreateCompilerPackageStats(depPackage));
+ getOrCreateCompilerPackageStats(depPackage),
+ mDexManager.isUsedByOtherApps(p.packageName));
}
}
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
- targetCompilerFilter, getOrCreateCompilerPackageStats(p));
+ targetCompilerFilter, getOrCreateCompilerPackageStats(p),
+ mDexManager.isUsedByOtherApps(p.packageName));
}
// Performs dexopt on the used secondary dex files belonging to the given package.
@@ -8710,6 +8719,7 @@
} catch (InstallerException e) {
Slog.w(TAG, String.valueOf(e));
}
+ mDexManager.notifyPackageDataDestroyed(pkg.packageName, userId);
}
}
@@ -9616,7 +9626,7 @@
/**
* Asserts the parsed package is valid according to the given policy. If the
- * package is invalid, for whatever reason, throws {@link PackgeManagerException}.
+ * package is invalid, for whatever reason, throws {@link PackageManagerException}.
* <p>
* Implementation detail: This method must NOT have any side effects. It would
* ideally be static, but, it requires locks to read system state.
@@ -12814,7 +12824,7 @@
* By having a field variable, we're able to track filter ordering as soon as
* a non-zero order is defined. Otherwise, multiple loops across the result set
* would be needed to apply ordering. If the intent resolver becomes re-entrant,
- * this needs to be contained entirely within {@link #filterResults()}.
+ * this needs to be contained entirely within {@link #filterResults}.
*/
final ArrayMap<String, Pair<Integer, EphemeralResolveInfo>> mOrderResult = new ArrayMap<>();
@@ -15909,14 +15919,6 @@
final PackageSetting ps = mSettings.mPackages.get(pkgName);
- // don't allow an upgrade from full to ephemeral
- if (isInstantApp && !ps.getInstantApp(user.getIdentifier())) {
- // can't downgrade from full to instant
- Slog.w(TAG, "Can't replace app with instant app: " + pkgName);
- res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
- return;
- }
-
// verify signatures are valid
if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
if (!checkUpgradeKeySetLP(ps, pkg)) {
@@ -15974,6 +15976,27 @@
// In case of rollback, remember per-user/profile install state
allUsers = sUserManager.getUserIds();
installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+ // don't allow an upgrade from full to ephemeral
+ if (isInstantApp) {
+ if (user == null || user.getIdentifier() == UserHandle.USER_ALL) {
+ for (int currentUser : allUsers) {
+ if (!ps.getInstantApp(currentUser)) {
+ // can't downgrade from full to instant
+ Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+ + " for user: " + currentUser);
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+ return;
+ }
+ }
+ } else if (!ps.getInstantApp(user.getIdentifier())) {
+ // can't downgrade from full to instant
+ Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+ + " for user: " + user.getIdentifier());
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+ return;
+ }
+ }
}
// Update what is removed
@@ -16125,6 +16148,8 @@
setInstantAppForUser(ps, user.getIdentifier(), instantApp, fullApp);
prepareAppDataAfterInstallLIF(newPackage);
addedPkg = true;
+ mDexManager.notifyPackageUpdated(newPackage.packageName,
+ newPackage.baseCodePath, newPackage.splitCodePaths);
} catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
}
@@ -16274,6 +16299,9 @@
updateSettingsLI(newPackage, installerPackageName, allUsers, res, user,
installReason);
prepareAppDataAfterInstallLIF(newPackage);
+
+ mDexManager.notifyPackageUpdated(newPackage.packageName,
+ newPackage.baseCodePath, newPackage.splitCodePaths);
}
} catch (PackageManagerException e) {
res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR);
@@ -16636,6 +16664,7 @@
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
+ pp.setCallback(mPackageParserCallback);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
@@ -16648,13 +16677,19 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
-// // Ephemeral apps must have target SDK >= O.
-// // TODO: Update conditional and error message when O gets locked down
-// if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
-// res.setError(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID,
-// "Ephemeral apps must have target SDK version of at least O");
-// return;
-// }
+ // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
+ if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ Slog.w(TAG, "Instant app package " + pkg.packageName
+ + " does not target O, this will be a fatal error.");
+ // STOPSHIP: Make this a fatal error
+ pkg.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ }
+ if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
+ Slog.w(TAG, "Instant app package " + pkg.packageName
+ + " does not target targetSandboxVersion 2, this will be a fatal error.");
+ // STOPSHIP: Make this a fatal error
+ pkg.applicationInfo.targetSandboxVersion = 2;
+ }
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// Static shared libraries have synthetic package names
@@ -16951,7 +16986,8 @@
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */, false /* checkProfiles */,
getCompilerFilterForReason(REASON_INSTALL),
- getOrCreateCompilerPackageStats(pkg));
+ getOrCreateCompilerPackageStats(pkg),
+ mDexManager.isUsedByOtherApps(pkg.packageName));
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Notify BackgroundDexOptJobService that the package has been changed.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9c9a671..0634dac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -80,9 +80,7 @@
try {
// Check that the system property name is legal.
String sysPropName = getSystemPropertyName(reason);
- if (sysPropName == null ||
- sysPropName.isEmpty() ||
- sysPropName.length() > SystemProperties.PROP_NAME_MAX) {
+ if (sysPropName == null || sysPropName.isEmpty()) {
throw new IllegalStateException("Reason system property name \"" +
sysPropName +"\" for reason " + REASON_STRINGS[reason]);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 45887e1..9feee8c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -133,7 +133,8 @@
sortTemp, packageManagerService);
// Give priority to apps used by other apps.
- applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result,
+ applyPackageFilter((pkg) ->
+ packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result,
remainingPkgs, sortTemp, packageManagerService);
// Filter out packages that aren't recently used, add all remaining apps.
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 6033855..4ff3e12 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -46,6 +46,7 @@
private final boolean mOnlyCore;
private final DisplayMetrics mMetrics;
private final File mCacheDir;
+ private final PackageParser.Callback mPackageParserCallback;
private volatile String mInterruptedInThread;
private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
@@ -54,11 +55,12 @@
"package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);
ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,
- DisplayMetrics metrics, File cacheDir) {
+ DisplayMetrics metrics, File cacheDir, PackageParser.Callback callback) {
mSeparateProcesses = separateProcesses;
mOnlyCore = onlyCoreApps;
mMetrics = metrics;
mCacheDir = cacheDir;
+ mPackageParserCallback = callback;
}
static class ParseResult {
@@ -110,6 +112,7 @@
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
+ pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
pr.pkg = parsePackage(pp, scanFile, parseFlags);
} catch (Throwable e) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index ac98ab9..7885748 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1635,10 +1635,10 @@
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
- if (si.hasMaskableBitmap() && !si.hasIconFile()) {
+ if (si.hasAdaptiveBitmap() && !si.hasIconFile()) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
- + " has maskable bitmap but was not saved to a file.");
+ + " has adaptive bitmap but was not saved to a file.");
}
if (si.hasIconFile() && si.hasIconResource()) {
failed = true;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 74eb340..43288cd 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -46,7 +46,6 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.pm.UserInfo;
@@ -1219,7 +1218,7 @@
shortcut.setIconResourceId(0);
shortcut.setIconResName(null);
shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
- ShortcutInfo.FLAG_MASKABLE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES);
+ ShortcutInfo.FLAG_ADAPTIVE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES);
}
public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
@@ -1355,7 +1354,7 @@
return;
}
case Icon.TYPE_BITMAP:
- case Icon.TYPE_BITMAP_MASKABLE: {
+ case Icon.TYPE_ADAPTIVE_BITMAP: {
bitmap = icon.getBitmap(); // Don't recycle in this case.
break;
}
@@ -1386,8 +1385,8 @@
shortcut.setBitmapPath(out.getFile().getAbsolutePath());
shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
- if (icon.getType() == Icon.TYPE_BITMAP_MASKABLE) {
- shortcut.addFlags(ShortcutInfo.FLAG_MASKABLE_BITMAP);
+ if (icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ shortcut.addFlags(ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
}
} finally {
IoUtils.closeQuietly(out);
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 00f3711..755c486 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageParser;
import android.os.RemoteException;
import android.os.storage.StorageManager;
+import android.os.UserHandle;
import android.util.Slog;
@@ -179,17 +180,64 @@
}
}
- public void notifyPackageInstalled(PackageInfo info, int userId) {
- cachePackageCodeLocation(info, userId);
+ /**
+ * Notifies that a new package was installed for {@code userId}.
+ * {@code userId} must not be {@code UserHandle.USER_ALL}.
+ *
+ * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
+ */
+ public void notifyPackageInstalled(PackageInfo pi, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ throw new IllegalArgumentException(
+ "notifyPackageInstalled called with USER_ALL");
+ }
+ cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir,
+ pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId);
}
- private void cachePackageCodeLocation(PackageInfo info, int userId) {
- PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName);
- if (pcl != null) {
- pcl.mergeAppDataDirs(info.applicationInfo, userId);
- } else {
- mPackageCodeLocationsCache.put(info.packageName,
- new PackageCodeLocations(info.applicationInfo, userId));
+ /**
+ * Notifies that package {@code packageName} was updated.
+ * This will clear the UsedByOtherApps mark if it exists.
+ */
+ public void notifyPackageUpdated(String packageName, String baseCodePath,
+ String[] splitCodePaths) {
+ cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
+ // In case there was an update, write the package use info to disk async.
+ // Note that we do the writing here and not in PackageDexUsage in order to be
+ // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
+ // multiple updates in PackaeDexUsage before writing it).
+ if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+ }
+
+ /**
+ * Notifies that the user {@code userId} data for package {@code packageName}
+ * was destroyed. This will remove all usage info associated with the package
+ * for the given user.
+ * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
+ * all usage information for the package will be removed.
+ */
+ public void notifyPackageDataDestroyed(String packageName, int userId) {
+ boolean updated = userId == UserHandle.USER_ALL
+ ? mPackageDexUsage.removePackage(packageName)
+ : mPackageDexUsage.removeUserPackage(packageName, userId);
+ // In case there was an update, write the package use info to disk async.
+ // Note that we do the writing here and not in PackageDexUsage in order to be
+ // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
+ // multiple updates in PackaeDexUsage before writing it).
+ if (updated) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+ }
+
+ public void cachePackageCodeLocation(String packageName, String baseCodePath,
+ String[] splitCodePaths, String dataDir, int userId) {
+ PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
+ new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
+ pcl.updateCodeLocation(baseCodePath, splitCodePaths);
+ if (dataDir != null) {
+ pcl.mergeAppDataDirs(dataDir, userId);
}
}
@@ -202,7 +250,8 @@
int userId = entry.getKey();
for (PackageInfo pi : packageInfoList) {
// Cache the code locations.
- cachePackageCodeLocation(pi, userId);
+ cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir,
+ pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId);
// Cache a map from package name to the set of user ids who installed the package.
// We will use it to sync the data and remove obsolete entries from
@@ -359,6 +408,23 @@
}
/**
+ * Return true if the profiling data collected for the given app indicate
+ * that the apps's APK has been loaded by another app.
+ * Note that this returns false for all apps without any collected profiling data.
+ */
+ public boolean isUsedByOtherApps(String packageName) {
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null) {
+ // No use info, means the package was not used or it was used but not by other apps.
+ // Note that right now we might prune packages which are not used by other apps.
+ // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
+ // to access the package use.
+ return false;
+ }
+ return useInfo.isUsedByOtherApps();
+ }
+
+ /**
* Retrieves the package which owns the given dexPath.
*/
private DexSearchResult getDexPackage(
@@ -408,27 +474,36 @@
*/
private static class PackageCodeLocations {
private final String mPackageName;
- private final String mBaseCodePath;
+ private String mBaseCodePath;
private final Set<String> mSplitCodePaths;
// Maps user id to the application private directory.
private final Map<Integer, Set<String>> mAppDataDirs;
public PackageCodeLocations(ApplicationInfo ai, int userId) {
- mPackageName = ai.packageName;
- mBaseCodePath = ai.sourceDir;
+ this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
+ mergeAppDataDirs(ai.dataDir, userId);
+ }
+ public PackageCodeLocations(String packageName, String baseCodePath,
+ String[] splitCodePaths) {
+ mPackageName = packageName;
mSplitCodePaths = new HashSet<>();
- if (ai.splitSourceDirs != null) {
- for (String split : ai.splitSourceDirs) {
+ mAppDataDirs = new HashMap<>();
+ updateCodeLocation(baseCodePath, splitCodePaths);
+ }
+
+ public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
+ mBaseCodePath = baseCodePath;
+ mSplitCodePaths.clear();
+ if (splitCodePaths != null) {
+ for (String split : splitCodePaths) {
mSplitCodePaths.add(split);
}
}
- mAppDataDirs = new HashMap<>();
- mergeAppDataDirs(ai, userId);
}
- public void mergeAppDataDirs(ApplicationInfo ai, int userId) {
+ public void mergeAppDataDirs(String dataDir, int userId) {
Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
- dataDirs.add(ai.dataDir);
+ dataDirs.add(dataDir);
}
public int searchDex(String dexPath, int userId) {
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 3693bce0..8a66f12 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -377,7 +377,34 @@
}
/**
+ * Clears the {@code usesByOtherApps} marker for the package {@code packageName}.
+ * @return true if the package usage info was updated.
+ */
+ public boolean clearUsedByOtherApps(String packageName) {
+ synchronized (mPackageUseInfoMap) {
+ PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+ if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) {
+ return false;
+ }
+ packageUseInfo.mIsUsedByOtherApps = false;
+ return true;
+ }
+ }
+
+ /**
+ * Remove the usage data associated with package {@code packageName}.
+ * @return true if the package usage was found and removed successfully.
+ */
+ public boolean removePackage(String packageName) {
+ synchronized (mPackageUseInfoMap) {
+ return mPackageUseInfoMap.remove(packageName) != null;
+ }
+ }
+
+ /**
* Remove all the records about package {@code packageName} belonging to user {@code userId}.
+ * If the package is left with no records of secondary dex usage and is not used by other
+ * apps it will be removed as well.
* @return true if the record was found and actually deleted,
* false if the record doesn't exist
*/
@@ -397,6 +424,12 @@
updated = true;
}
}
+ // If no secondary dex info is left and the package is not used by other apps
+ // remove the data since it is now useless.
+ if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) {
+ mPackageUseInfoMap.remove(packageName);
+ updated = true;
+ }
return updated;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0d6cd80..4f1754a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2398,8 +2398,8 @@
* permission to add alert windows that aren't
* {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}.
*/
- return (mContext.checkCallingPermission(INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED)
- ? ADD_OKAY : ADD_PERMISSION_DENIED;
+ return (mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
+ == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED;
}
// check if user has enabled this operation. SecurityException will be thrown if this app
@@ -2420,8 +2420,8 @@
default:
// in the default mode, we will make a decision here based on
// checkCallingPermission()
- return (mContext.checkCallingPermission(SYSTEM_ALERT_WINDOW) == PERMISSION_GRANTED)
- ? ADD_OKAY : ADD_PERMISSION_DENIED;
+ return (mContext.checkCallingOrSelfPermission(SYSTEM_ALERT_WINDOW)
+ == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED;
}
}
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 10d30aa..c064392 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -34,21 +34,37 @@
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.DateUtils;
+import android.util.Pair;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-
/**
* CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground
* time using the calculation as defined in the refuel rocket.
@@ -58,17 +74,28 @@
private final Object mLock = new Object();
+ // XML Constants
+ private static final String CACHE_INFO_TAG = "cache-info";
+ private static final String ATTR_PREVIOUS_BYTES = "previousBytes";
+ private static final String TAG_QUOTA = "quota";
+ private static final String ATTR_UUID = "uuid";
+ private static final String ATTR_UID = "uid";
+ private static final String ATTR_QUOTA_IN_BYTES = "bytes";
+
private final Context mContext;
private final UsageStatsManagerInternal mUsageStats;
private final Installer mInstaller;
private ServiceConnection mServiceConnection;
private ICacheQuotaService mRemoteService;
+ private AtomicFile mPreviousValuesFile;
public CacheQuotaStrategy(
Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) {
mContext = Preconditions.checkNotNull(context);
mUsageStats = Preconditions.checkNotNull(usageStatsManager);
mInstaller = Preconditions.checkNotNull(installer);
+ mPreviousValuesFile = new AtomicFile(new File(
+ new File(Environment.getDataDirectory(), "system"), "cachequota.xml"));
}
/**
@@ -128,7 +155,7 @@
}
/**
- * Returns a list of CacheQuotaRequests which do not have their quotas filled out for apps
+ * Returns a list of CacheQuotaHints which do not have their quotas filled out for apps
* which have been used in the last year.
*/
private List<CacheQuotaHint> getUnfulfilledRequests() {
@@ -176,6 +203,11 @@
final List<CacheQuotaHint> processedRequests =
data.getParcelableArrayList(
CacheQuotaService.REQUEST_LIST_KEY);
+ pushProcessedQuotas(processedRequests);
+ writeXmlToFile(processedRequests);
+ }
+
+ private void pushProcessedQuotas(List<CacheQuotaHint> processedRequests) {
final int requestSize = processedRequests.size();
for (int i = 0; i < requestSize; i++) {
CacheQuotaHint request = processedRequests.get(i);
@@ -200,8 +232,10 @@
}
private void disconnectService() {
- mContext.unbindService(mServiceConnection);
- mServiceConnection = null;
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
}
private ComponentName getServiceComponentName() {
@@ -223,4 +257,131 @@
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
}
+
+ private void writeXmlToFile(List<CacheQuotaHint> processedRequests) {
+ FileOutputStream fileStream = null;
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ fileStream = mPreviousValuesFile.startWrite();
+ out.setOutput(fileStream, StandardCharsets.UTF_8.name());
+ saveToXml(out, processedRequests, 0);
+ mPreviousValuesFile.finishWrite(fileStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "An error occurred while writing the cache quota file.", e);
+ mPreviousValuesFile.failWrite(fileStream);
+ }
+ }
+
+ /**
+ * Initializes the quotas from the file.
+ * @return the number of bytes that were free on the device when the quotas were last calced.
+ */
+ public long setupQuotasFromFile() throws IOException {
+ FileInputStream stream;
+ try {
+ stream = mPreviousValuesFile.openRead();
+ } catch (FileNotFoundException e) {
+ // The file may not exist yet -- this isn't truly exceptional.
+ return -1;
+ }
+
+ Pair<Long, List<CacheQuotaHint>> cachedValues = null;
+ try {
+ cachedValues = readFromXml(stream);
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+
+ if (cachedValues == null) {
+ Slog.e(TAG, "An error occurred while parsing the cache quota file.");
+ return -1;
+ }
+ pushProcessedQuotas(cachedValues.second);
+ return cachedValues.first;
+ }
+
+ @VisibleForTesting
+ static void saveToXml(XmlSerializer out,
+ List<CacheQuotaHint> requests, long bytesWhenCalculated) throws IOException {
+ out.startDocument(null, true);
+ out.startTag(null, CACHE_INFO_TAG);
+ int requestSize = requests.size();
+ out.attribute(null, ATTR_PREVIOUS_BYTES, Long.toString(bytesWhenCalculated));
+
+ for (int i = 0; i < requestSize; i++) {
+ CacheQuotaHint request = requests.get(i);
+ out.startTag(null, TAG_QUOTA);
+ String uuid = request.getVolumeUuid();
+ if (uuid != null) {
+ out.attribute(null, ATTR_UUID, request.getVolumeUuid());
+ }
+ out.attribute(null, ATTR_UID, Integer.toString(request.getUid()));
+ out.attribute(null, ATTR_QUOTA_IN_BYTES, Long.toString(request.getQuota()));
+ out.endTag(null, TAG_QUOTA);
+ }
+ out.endTag(null, CACHE_INFO_TAG);
+ out.endDocument();
+ }
+
+ protected static Pair<Long, List<CacheQuotaHint>> readFromXml(InputStream inputStream)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ Slog.d(TAG, "No quotas found in quota file.");
+ return null;
+ }
+
+ String tagName = parser.getName();
+ if (!CACHE_INFO_TAG.equals(tagName)) {
+ throw new IllegalStateException("Invalid starting tag.");
+ }
+
+ final List<CacheQuotaHint> quotas = new ArrayList<>();
+ long previousBytes;
+ try {
+ previousBytes = Long.parseLong(parser.getAttributeValue(
+ null, ATTR_PREVIOUS_BYTES));
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(
+ "Previous bytes formatted incorrectly; aborting quota read.");
+ }
+
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (TAG_QUOTA.equals(tagName)) {
+ CacheQuotaHint request = getRequestFromXml(parser);
+ if (request == null) {
+ continue;
+ }
+ quotas.add(request);
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ return new Pair<>(previousBytes, quotas);
+ }
+
+ @VisibleForTesting
+ static CacheQuotaHint getRequestFromXml(XmlPullParser parser) {
+ try {
+ String uuid = parser.getAttributeValue(null, ATTR_UUID);
+ int uid = Integer.parseInt(parser.getAttributeValue(null, ATTR_UID));
+ long bytes = Long.parseLong(parser.getAttributeValue(null, ATTR_QUOTA_IN_BYTES));
+ return new CacheQuotaHint.Builder()
+ .setVolumeUuid(uuid).setUid(uid).setQuota(bytes).build();
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Invalid cache quota request, skipping.");
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java
index 0d282ef..b7b419b 100644
--- a/services/core/java/com/android/server/wm/AlertWindowNotification.java
+++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.content.Context.NOTIFICATION_SERVICE;
@@ -34,8 +33,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
import com.android.internal.R;
+import com.android.server.policy.IconUtilities;
/** Displays an ongoing notification for a process displaying an alert window */
class AlertWindowNotification {
@@ -50,6 +51,7 @@
private final String mPackageName;
private final int mUid;
private boolean mCancelled;
+ private IconUtilities mIconUtilities;
AlertWindowNotification(WindowManagerService service, String packageName, int uid) {
mService = service;
@@ -59,20 +61,33 @@
(NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE);
mNotificationTag = CHANNEL_PREFIX + mPackageName;
mRequestCode = sNextRequestCode++;
+ mIconUtilities = new IconUtilities(mService.mContext);
// We can't create/post the notification while the window manager lock is held since it will
// end up calling into activity manager. So, we post a message to do it later.
- mService.mH.post(this::postNotification);
+ mService.mH.post(this::onPostNotification);
}
/** Cancels the notification */
void cancel() {
+ // We can't call into NotificationManager with WM lock held since it might call into AM.
+ // So, we post a message to do it later.
+ mService.mH.post(this::onCancelNotification);
+ }
+
+ /** Don't call with the window manager lock held! */
+ private void onCancelNotification() {
mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID);
mCancelled = true;
}
/** Don't call with the window manager lock held! */
- private void postNotification() {
+ private void onPostNotification() {
+ if (mCancelled) {
+ // Notification was cancelled, so nothing more to do...
+ return;
+ }
+
final Context context = mService.mContext;
final PackageManager pm = context.getPackageManager();
final ApplicationInfo aInfo = getApplicationInfo(pm, mPackageName);
@@ -94,17 +109,14 @@
.addAction(getTurnOffAction(context, mPackageName, mUid));
if (aInfo != null) {
- final Bitmap bitmap = ((BitmapDrawable) pm.getApplicationIcon(aInfo)).getBitmap();
- builder.setLargeIcon(bitmap);
+ final Drawable drawable = pm.getApplicationIcon(aInfo);
+ if (drawable != null) {
+ final Bitmap bitmap = mIconUtilities.createIconBitmap(drawable);
+ builder.setLargeIcon(bitmap);
+ }
}
- synchronized (mService.mWindowMap) {
- if (mCancelled) {
- // Notification was cancelled, so nothing more to do...
- return;
- }
- mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build());
- }
+ mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build());
}
private Notification.Action getTurnOffAction(Context context, String packageName, int uid) {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 3a86874..266ab4c 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -525,7 +525,8 @@
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
- mContainer.mTask.mTaskId, mContainer.mTask.mUserId, false /* restoreFromDisk */);
+ mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
+ false /* restoreFromDisk */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e67c91e..647adbf 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -84,14 +84,16 @@
final boolean mVoiceInteraction;
- // TODO: Use getParent instead?
- Task mTask;
/** @see WindowContainer#fillsParent() */
private boolean mFillsParent;
boolean layoutConfigChanges;
boolean mShowForAllUsers;
int mTargetSdk;
+ // Flag set while reparenting to prevent actions normally triggered by an individual parent
+ // change.
+ private boolean mReparenting;
+
// The input dispatching timeout for this application token in nanoseconds.
long mInputDispatchingTimeoutNanos;
@@ -407,7 +409,7 @@
}
boolean windowsAreFocusable() {
- return StackId.canReceiveKeys(mTask.mStack.mStackId) || mAlwaysFocusable;
+ return StackId.canReceiveKeys(getTask().mStack.mStackId) || mAlwaysFocusable;
}
AppWindowContainerController getController() {
@@ -426,10 +428,7 @@
void removeIfPossible() {
mIsExiting = false;
removeAllWindowsIfPossible();
- if (mTask != null) {
- mTask.mStack.mExitingAppTokens.remove(this);
- removeImmediately();
- }
+ removeImmediately();
}
@Override
@@ -466,7 +465,7 @@
getController().removeStartingWindow();
}
- final TaskStack stack = mTask.mStack;
+ final TaskStack stack = getTask().mStack;
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM,
@@ -483,6 +482,7 @@
removed = true;
stopFreezingScreen(true, true);
+
if (mService.mFocusedApp == this) {
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this);
mService.mFocusedApp = null;
@@ -664,6 +664,29 @@
allDrawnExcludingSaved = false;
}
+ Task getTask() {
+ return (Task) getParent();
+ }
+
+ @Override
+ void onParentSet() {
+ super.onParentSet();
+
+ // When the associated task is {@code null}, the {@link AppWindowToken} can no longer
+ // access visual elements like the {@link DisplayContent}. We must remove any associations
+ // such as animations.
+ if (!mReparenting) {
+ final Task task = getTask();
+ if (task == null) {
+ // It is possible we have been marked as a closing app earlier. We must remove ourselves
+ // from this list so we do not participate in any future animations.
+ mService.mClosingApps.remove(this);
+ } else if (task.mStack != null) {
+ task.mStack.mExitingAppTokens.remove(this);
+ }
+ }
+ }
+
void postWindowRemoveStartingWindowCleanup(WindowState win) {
// TODO: Something smells about the code below...Is there a better way?
if (startingWindow == win) {
@@ -762,16 +785,6 @@
if (canFreezeBounds()) {
freezeBounds();
}
-
- // In the process of tearing down before relaunching, the app will
- // try and clean up it's child surfaces. We need to prevent this from
- // happening, so we sever the children, transfering their ownership
- // from the client it-self to the parent surface (owned by us).
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- w.mWinAnimator.detachChildren();
- }
-
mPendingRelaunchCount++;
}
@@ -862,17 +875,29 @@
}
void reparent(Task task, int position) {
- if (task == mTask) {
+ final Task currentTask = getTask();
+ if (task == currentTask) {
throw new IllegalArgumentException(
- "window token=" + this + " already child of task=" + mTask);
+ "window token=" + this + " already child of task=" + currentTask);
}
+
+ if (currentTask.mStack != task.mStack) {
+ throw new IllegalArgumentException(
+ "window token=" + this + " current task=" + currentTask
+ + " belongs to a different stack than " + task);
+ }
+
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "reParentWindowToken: removing window token=" + this
- + " from task=" + mTask);
+ + " from task=" + currentTask);
final DisplayContent prevDisplayContent = getDisplayContent();
+ mReparenting = true;
+
getParent().removeChild(this);
task.addChild(this, position);
+ mReparenting = false;
+
// Relayout display(s).
final DisplayContent displayContent = task.getDisplayContent();
displayContent.setLayoutNeeded();
@@ -883,9 +908,11 @@
}
private boolean canFreezeBounds() {
+ final Task task = getTask();
+
// For freeform windows, we can't freeze the bounds at the moment because this would make
// the resizing unresponsive.
- return mTask != null && !mTask.inFreeformWorkspace();
+ return task != null && !task.inFreeformWorkspace();
}
/**
@@ -895,16 +922,17 @@
* with a queue.
*/
private void freezeBounds() {
- mFrozenBounds.offer(new Rect(mTask.mPreparedFrozenBounds));
+ final Task task = getTask();
+ mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds));
- if (mTask.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
+ if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
// We didn't call prepareFreezingBounds on the task, so use the current value.
- mFrozenMergedConfig.offer(new Configuration(mTask.getConfiguration()));
+ mFrozenMergedConfig.offer(new Configuration(task.getConfiguration()));
} else {
- mFrozenMergedConfig.offer(new Configuration(mTask.mPreparedFrozenMergedConfig));
+ mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig));
}
// Calling unset() to make it equal to Configuration.EMPTY.
- mTask.mPreparedFrozenMergedConfig.unset();
+ task.mPreparedFrozenMergedConfig.unset();
}
/**
@@ -1429,7 +1457,7 @@
if (appToken != null) {
pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
}
- pw.print(prefix); pw.print("task="); pw.println(mTask);
+ pw.print(prefix); pw.print("task="); pw.println(getTask());
pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent);
pw.print(" mOrientation="); pw.println(mOrientation);
pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 75a79fd..aa8e3774 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -588,7 +588,7 @@
private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
for (int i = apps.size() - 1; i >= 0; i--) {
final AppWindowToken token = apps.valueAt(i);
- if (token.mTask != null && token.mTask.mStack.mStackId == DOCKED_STACK_ID) {
+ if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5355f31..720a454 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -93,7 +93,7 @@
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
- mCanAddInternalSystemWindow = service.mContext.checkCallingPermission(
+ mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
StringBuilder sb = new StringBuilder();
sb.append("Session{");
@@ -664,6 +664,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+ pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
pw.print(" mClientDead="); pw.print(mClientDead);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index da5fcf3..99c085f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -133,7 +133,6 @@
void addChild(AppWindowToken wtoken, int position) {
position = getAdjustedAddPosition(position);
super.addChild(wtoken, position);
- wtoken.mTask = this;
mDeferRemoval = false;
}
@@ -244,7 +243,6 @@
removeIfPossible();
}
}
- token.mTask = null;
}
void setSendingToBottom(boolean toBottom) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 601bf28..4028336 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -106,8 +106,8 @@
if (taskId != null) {
removeRunningEntry(taskId);
}
- if (wtoken.mTask != null) {
- mRetrievalCache.remove(wtoken.mTask.mTaskId);
+ if (wtoken.getTask() != null) {
+ mRetrievalCache.remove(wtoken.getTask().mTaskId);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5041138..5995bba 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -141,11 +141,12 @@
outClosingTasks.clear();
for (int i = closingApps.size() - 1; i >= 0; i--) {
final AppWindowToken atoken = closingApps.valueAt(i);
+ final Task task = atoken.getTask();
// If the task of the app is not visible anymore, it means no other app in that task
// is opening. Thus, the task is closing.
- if (atoken.mTask != null && !atoken.mTask.isVisible()) {
- outClosingTasks.add(closingApps.valueAt(i).mTask);
+ if (task != null && !task.isVisible()) {
+ outClosingTasks.add(task);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 9f52412..9f34bd7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -96,9 +96,11 @@
// TODO: Inherit behavior whether to draw behind status bar/nav bar.
layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
- if (token.mTask != null) {
- final TaskDescription taskDescription = token.mTask.getTaskDescription();
+ final Task task = token.getTask();
+ if (task != null) {
+ layoutParams.setTitle(String.format(TITLE_FORMAT,task.mTaskId));
+
+ final TaskDescription taskDescription = task.getTaskDescription();
if (taskDescription != null) {
fillBackgroundColor = taskDescription.getBackgroundColor();
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b79ea71..7588b71 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -644,7 +644,7 @@
}
for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
- if (wtoken.mTask == task) {
+ if (wtoken.getTask() == task) {
wtoken.mIsExiting = false;
mExitingAppTokens.remove(appNdx);
}
@@ -1439,6 +1439,14 @@
mBoundsAnimating = true;
mBoundsAnimatingToFullscreen = toFullscreen;
}
+
+ if (mStackId == PINNED_STACK_ID) {
+ try {
+ mService.mActivityManager.notifyPinnedStackAnimationStarted();
+ } catch (RemoteException e) {
+ // I don't believe you...
+ }
+ }
}
@Override // AnimatesBounds
@@ -1448,6 +1456,7 @@
mBoundsAnimationTarget.setEmpty();
mService.requestTraversal();
}
+
if (mStackId == PINNED_STACK_ID) {
try {
mService.mActivityManager.notifyPinnedStackAnimationEnded();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index eb10f0c..e339329 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1445,9 +1445,9 @@
if (displayContent.isDefaultDisplay) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final Rect taskBounds;
- if (atoken != null && atoken.mTask != null) {
+ if (atoken != null && atoken.getTask() != null) {
taskBounds = mTmpRect;
- atoken.mTask.getBounds(mTmpRect);
+ atoken.getTask().getBounds(mTmpRect);
} else {
taskBounds = null;
}
@@ -2199,15 +2199,6 @@
if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
-
- // When we start the exit animation we take the Surface from the client
- // so it will stop perturbing it. We need to likewise takeaway the SurfaceFlinger
- // side child surfaces, so they will remain preserved in their current state
- // (rather than be cleaned up immediately by the app code).
- SurfaceControl.openTransaction();
- winAnimator.detachChildren();
- SurfaceControl.closeTransaction();
-
return focusMayChange;
}
@@ -2292,7 +2283,7 @@
// is running.
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
if (okToDisplay()) {
- final DisplayContent displayContent = atoken.mTask.getDisplayContent();
+ final DisplayContent displayContent = atoken.getTask().getDisplayContent();
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int width = displayInfo.appWidth;
final int height = displayInfo.appHeight;
@@ -2333,7 +2324,7 @@
final Configuration displayConfig = displayContent.getConfiguration();
Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
- isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+ isVoiceInteraction, freeform, atoken.getTask().mTaskId);
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
final int containingWidth = frame.width();
@@ -2553,8 +2544,8 @@
}
void setFocusTaskRegionLocked(AppWindowToken previousFocus) {
- final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
- final Task previousTask = previousFocus != null ? previousFocus.mTask : null;
+ final Task focusedTask = mFocusedApp != null ? mFocusedApp.getTask() : null;
+ final Task previousTask = previousFocus != null ? previousFocus.getTask() : null;
final DisplayContent focusedDisplayContent =
focusedTask != null ? focusedTask.getDisplayContent() : null;
final DisplayContent previousDisplayContent =
@@ -5085,8 +5076,8 @@
// Also don't use mInputMethodTarget's stack, because some window with FLAG_NOT_FOCUSABLE
// and FLAG_ALT_FOCUSABLE_IM flags both set might be set to IME target so they're moved
// to make room for IME, but the window is not the focused window that's taking input.
- return (mFocusedApp != null && mFocusedApp.mTask != null) ?
- mFocusedApp.mTask.mStack : null;
+ return (mFocusedApp != null && mFocusedApp.getTask() != null) ?
+ mFocusedApp.getTask().mStack : null;
}
public boolean detectSafeMode() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 14f14c5..6d572d7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -844,20 +844,24 @@
// Make sure the content and visible frames are inside of the
// final window frame.
if (windowsAreFloating && !mFrame.isEmpty()) {
+ // For pinned workspace the frame isn't limited in any particular
+ // way since SystemUI controls the bounds. For freeform however
+ // we want to keep things inside the content frame.
+ final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
// Keep the frame out of the blocked system area, limit it in size to the content area
// and make sure that there is always a minimum visible so that the user can drag it
// into a usable area..
- final int height = Math.min(mFrame.height(), mContentFrame.height());
- final int width = Math.min(mContentFrame.width(), mFrame.width());
+ final int height = Math.min(mFrame.height(), limitFrame.height());
+ final int width = Math.min(limitFrame.width(), mFrame.width());
final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
final int minVisibleHeight = Math.min(height, WindowManagerService.dipToPixel(
MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics));
final int minVisibleWidth = Math.min(width, WindowManagerService.dipToPixel(
MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics));
- final int top = Math.max(mContentFrame.top,
- Math.min(mFrame.top, mContentFrame.bottom - minVisibleHeight));
- final int left = Math.max(mContentFrame.left + minVisibleWidth - width,
- Math.min(mFrame.left, mContentFrame.right - minVisibleWidth));
+ final int top = Math.max(limitFrame.top,
+ Math.min(mFrame.top, limitFrame.bottom - minVisibleHeight));
+ final int left = Math.max(limitFrame.left + minVisibleWidth - width,
+ Math.min(mFrame.left, limitFrame.right - minVisibleWidth));
mFrame.set(left, top, left + width, top + height);
mContentFrame.set(mFrame);
mVisibleFrame.set(mContentFrame);
@@ -1202,7 +1206,7 @@
}
Task getTask() {
- return mAppToken != null ? mAppToken.mTask : null;
+ return mAppToken != null ? mAppToken.getTask() : null;
}
TaskStack getStack() {
@@ -2378,8 +2382,8 @@
/** @return true if this window desires touch events. */
boolean canReceiveTouchInput() {
- return mAppToken != null && mAppToken.mTask != null
- && mAppToken.mTask.mStack.shouldIgnoreInput();
+ return mAppToken != null && mAppToken.getTask() != null
+ && mAppToken.getTask().mStack.shouldIgnoreInput();
}
@Override
@@ -2712,29 +2716,32 @@
destroyedSomething |= c.destroySurface(cleanupOnResume, appStopped);
}
- if (appStopped || mWindowRemovalAllowed || cleanupOnResume) {
-
- mWinAnimator.destroyPreservedSurfaceLocked();
-
- if (mDestroying) {
- if (DEBUG_ADD_REMOVE) Slog.e(TAG_WM, "win=" + this
- + " destroySurfaces: appStopped=" + appStopped
- + " win.mWindowRemovalAllowed=" + mWindowRemovalAllowed
- + " win.mRemoveOnExit=" + mRemoveOnExit);
-
- if (!cleanupOnResume || mRemoveOnExit) {
- destroyOrSaveSurface();
- }
- if (mRemoveOnExit) {
- removeImmediately();
- }
- if (cleanupOnResume) {
- requestUpdateWallpaperIfNeeded();
- }
- mDestroying = false;
- destroyedSomething = true;
- }
+ if (!(appStopped || mWindowRemovalAllowed || cleanupOnResume)) {
+ return destroyedSomething;
}
+
+ if (appStopped || mWindowRemovalAllowed) {
+ mWinAnimator.destroyPreservedSurfaceLocked();
+ }
+
+ if (mDestroying) {
+ if (DEBUG_ADD_REMOVE) Slog.e(TAG_WM, "win=" + this
+ + " destroySurfaces: appStopped=" + appStopped
+ + " win.mWindowRemovalAllowed=" + mWindowRemovalAllowed
+ + " win.mRemoveOnExit=" + mRemoveOnExit);
+ if (!cleanupOnResume || mRemoveOnExit) {
+ destroyOrSaveSurface();
+ }
+ if (mRemoveOnExit) {
+ removeImmediately();
+ }
+ if (cleanupOnResume) {
+ requestUpdateWallpaperIfNeeded();
+ }
+ mDestroying = false;
+ destroyedSomething = true;
+ }
+
return destroyedSomething;
}
@@ -4378,7 +4385,8 @@
// will keep their surface and its size may change over time.
if (mHasSurface && !isChildWindow()) {
mWinAnimator.preserveSurfaceLocked();
- result |= RELAYOUT_RES_FIRST_TIME;
+ result |= RELAYOUT_RES_SURFACE_CHANGED |
+ RELAYOUT_RES_FIRST_TIME;
}
}
final boolean freeformResizing = isDragResizing()
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 4b71338..98598e1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -566,20 +566,6 @@
if (!mDestroyPreservedSurfaceUponRedraw) {
return;
}
- if (mSurfaceController != null) {
- if (mPendingDestroySurface != null) {
- // If we are preserving a surface but we aren't relaunching that means
- // we are just doing an in-place switch. In that case any SurfaceFlinger side
- // child layers need to be reparented to the new surface to make this
- // transparent to the app.
- if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) {
- SurfaceControl.openTransaction();
- mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController);
- SurfaceControl.closeTransaction();
- }
- }
- }
-
destroyDeferredSurfaceLocked();
mDestroyPreservedSurfaceUponRedraw = false;
}
@@ -1979,10 +1965,4 @@
}
return mForceScaleUntilResize;
}
-
- void detachChildren() {
- if (mSurfaceController != null) {
- mSurfaceController.detachChildren();
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index f7d3343..f8e7428 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -135,20 +135,6 @@
}
}
- void reparentChildrenInTransaction(WindowSurfaceController other) {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, "REPARENT from: " + this + " to: " + other);
- if ((mSurfaceControl != null) && (other.mSurfaceControl != null)) {
- mSurfaceControl.reparentChildren(other.getHandle());
- }
- }
-
- void detachChildren() {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, "SEVER CHILDREN");
- if (mSurfaceControl != null) {
- mSurfaceControl.detachChildren();
- }
- }
-
void hideInTransaction(String reason) {
if (SHOW_TRANSACTIONS) logSurface("HIDE ( " + reason + " )", null);
mHiddenForOtherReasons = true;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 897d5b86..f247ebe 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -651,7 +651,7 @@
if (openingAppAnimator == null || openingAppAnimator.animation == null) {
return;
}
- final int taskId = appToken.mTask.mTaskId;
+ final int taskId = appToken.getTask().mTaskId;
Bitmap thumbnailHeader = mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
if (thumbnailHeader == null || thumbnailHeader.getConfig() == Bitmap.Config.ALPHA_8) {
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index 74af639..813dcf5 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -43,28 +43,86 @@
static bool validate(jint light, jint flash, jint brightness) {
bool valid = true;
- if (light < 0 || light >= static_cast<int>(Type::COUNT)) {
+ if (light < 0 || light >= static_cast<jint>(Type::COUNT)) {
ALOGE("Invalid light parameter %d.", light);
valid = false;
}
- if (flash != static_cast<int>(Flash::NONE) &&
- flash != static_cast<int>(Flash::TIMED) &&
- flash != static_cast<int>(Flash::HARDWARE)) {
+ if (flash != static_cast<jint>(Flash::NONE) &&
+ flash != static_cast<jint>(Flash::TIMED) &&
+ flash != static_cast<jint>(Flash::HARDWARE)) {
ALOGE("Invalid flash parameter %d.", flash);
valid = false;
}
- if (brightness != static_cast<int>(Brightness::USER) &&
- brightness != static_cast<int>(Brightness::SENSOR) &&
- brightness != static_cast<int>(Brightness::LOW_PERSISTENCE)) {
+ if (brightness != static_cast<jint>(Brightness::USER) &&
+ brightness != static_cast<jint>(Brightness::SENSOR) &&
+ brightness != static_cast<jint>(Brightness::LOW_PERSISTENCE)) {
ALOGE("Invalid brightness parameter %d.", brightness);
valid = false;
}
+ if (brightness == static_cast<jint>(Brightness::LOW_PERSISTENCE) &&
+ light != static_cast<jint>(Type::BACKLIGHT)) {
+ ALOGE("Cannot set low-persistence mode for non-backlight device.");
+ valid = false;
+ }
+
return valid;
}
+static LightState constructState(
+ jint colorARGB,
+ jint flashMode,
+ jint onMS,
+ jint offMS,
+ jint brightnessMode){
+ Flash flash = static_cast<Flash>(flashMode);
+ Brightness brightness = static_cast<Brightness>(brightnessMode);
+
+ LightState state{};
+
+ if (brightness == Brightness::LOW_PERSISTENCE) {
+ state.flashMode = Flash::NONE;
+ } else {
+ // Only set non-brightness settings when not in low-persistence mode
+ state.flashMode = flash;
+ state.flashOnMs = onMS;
+ state.flashOffMs = offMS;
+ }
+
+ state.color = colorARGB;
+ state.brightnessMode = brightness;
+
+ return state;
+}
+
+static void processReturn(
+ const Return<Status> &ret,
+ Type type,
+ const LightState &state) {
+ if (!ret.isOk()) {
+ ALOGE("Failed to issue set light command.");
+ gLight = nullptr;
+ return;
+ }
+
+ switch (static_cast<Status>(ret)) {
+ case Status::SUCCESS:
+ break;
+ case Status::LIGHT_NOT_SUPPORTED:
+ ALOGE("Light requested not available on this device. %d", type);
+ break;
+ case Status::BRIGHTNESS_NOT_SUPPORTED:
+ ALOGE("Brightness parameter not supported on this device: %d",
+ state.brightnessMode);
+ break;
+ case Status::UNKNOWN:
+ default:
+ ALOGE("Unknown error setting light.");
+ }
+}
+
static void setLight_native(
JNIEnv* /* env */,
jobject /* clazz */,
@@ -79,63 +137,23 @@
return;
}
- // TODO(b/31632518)
- if (gLight == nullptr) {
+ if (gLight == nullptr || !gLight->ping().isOk()) {
gLight = ILight::getService();
}
if (gLight == nullptr) {
- ALOGE("LightService unable to get ILight interface.");
+ ALOGE("Unable to get ILight interface.");
return;
}
Type type = static_cast<Type>(light);
- Flash flash = static_cast<Flash>(flashMode);
- Brightness brightness = static_cast<Brightness>(brightnessMode);
+ LightState state = constructState(
+ colorARGB, flashMode, onMS, offMS, brightnessMode);
- LightState state{};
-
- if (brightnessMode == static_cast<int>(Brightness::LOW_PERSISTENCE)) {
- if (light != static_cast<int>(Type::BACKLIGHT)) {
- ALOGE("Cannot set low-persistence mode for non-backlight device.");
- return;
- }
- state.flashMode = Flash::NONE;
- } else {
- // Only set non-brightness settings when not in low-persistence mode
- state.flashMode = flash;
- state.flashOnMs = onMS;
- state.flashOffMs = offMS;
- }
-
- state.color = colorARGB;
- state.brightnessMode = brightness;
-
- Status status;
{
ALOGD_IF_SLOW(50, "Excessive delay setting light");
Return<Status> ret = gLight->setLight(type, state);
-
- if (!ret.isOk()) {
- ALOGE("Failed to issue set light command.");
- return;
- }
-
- status = static_cast<Status>(ret); // hal status
- }
-
- switch (status) {
- case Status::SUCCESS:
- break;
- case Status::LIGHT_NOT_SUPPORTED:
- ALOGE("Light requested not availale on this device.");
- break;
- case Status::BRIGHTNESS_NOT_SUPPORTED:
- ALOGE("Brightness parameter not supported on this device.");
- break;
- case Status::UNKNOWN:
- default:
- ALOGE("Unknown error setting light.");
+ processReturn(ret, type, state);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8b94ca0..9c3ecd0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -166,6 +167,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -243,10 +245,14 @@
private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set";
+ private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+
private static final String ATTR_ID = "id";
private static final String ATTR_VALUE = "value";
+ private static final String ATTR_ALIAS = "alias";
+
private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
@@ -480,6 +486,7 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
// This is the list of component allowed to start lock task mode.
@@ -504,6 +511,9 @@
boolean mDefaultInputMethodSet = false;
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+ Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
// Used for initialization of users created by createAndManageUsers.
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
@@ -518,6 +528,7 @@
final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
final Handler mHandler;
+ final Handler mBackgroundHandler;
/** Listens on any device, even when mHasFeature == false. */
final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
@@ -619,6 +630,25 @@
handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
+ } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
+ mBackgroundHandler.post(() -> {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ UserHandle.of(userHandle))) {
+ final List<String> caCerts =
+ keyChainConnection.getService().getUserCaAliases().getList();
+ synchronized (DevicePolicyManagerService.this) {
+ if (getUserData(userHandle).mOwnerInstalledCaCerts
+ .retainAll(caCerts)) {
+ saveSettingsLocked(userHandle);
+ }
+ }
+ } catch (InterruptedException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ Thread.currentThread().interrupt();
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ }
+ });
}
}
@@ -1786,6 +1816,7 @@
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ mBackgroundHandler = BackgroundThread.getHandler();
// Broadcast filter for changes to the trusted certificate store. These changes get a
// separate intent filter so we can listen to them even when device_admin is off.
@@ -1807,6 +1838,7 @@
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2580,6 +2612,12 @@
out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
}
+ for (final String cert : policy.mOwnerInstalledCaCerts) {
+ out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ out.attribute(null, ATTR_ALIAS, cert);
+ out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -2696,6 +2734,7 @@
policy.mAdminList.clear();
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
+ policy.mOwnerInstalledCaCerts.clear();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -2791,6 +2830,8 @@
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) {
policy.mDefaultInputMethodSet = true;
+ } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+ policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -4691,17 +4732,15 @@
return false;
}
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
final long id = mInjector.binderClearCallingIdentity();
+ String alias = null;
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
- keyChainConnection.getService().installCaCertificate(pemCert);
- return true;
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
+ alias = keyChainConnection.getService().installCaCertificate(pemCert);
} catch (RemoteException e) {
Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
- } finally {
- keyChainConnection.close();
}
} catch (InterruptedException e1) {
Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
@@ -4709,6 +4748,16 @@
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ if (alias == null) {
+ Log.w(LOG_TAG, "Problem installing cert");
+ } else {
+ synchronized (this) {
+ final int userId = userHandle.getIdentifier();
+ getUserData(userId).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(userId);
+ }
+ return true;
+ }
return false;
}
@@ -4722,25 +4771,31 @@
public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
enforceCanManageCaCerts(admin, callerPackage);
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final int userId = mInjector.userHandleGetCallingUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
for (int i = 0 ; i < aliases.length; i++) {
keyChainConnection.getService().deleteCaCertificate(aliases[i]);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
- } finally {
- keyChainConnection.close();
+ return;
}
} catch (InterruptedException ie) {
Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
Thread.currentThread().interrupt();
+ return;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ synchronized (this) {
+ if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+ saveSettingsLocked(userId);
+ }
+ }
}
@Override
@@ -6731,6 +6786,7 @@
}
final DevicePolicyData policyData = getUserData(userId);
policyData.mDefaultInputMethodSet = false;
+ policyData.mOwnerInstalledCaCerts.clear();
saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
@@ -7127,29 +7183,32 @@
}
private void enforceFullCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
private void enforceCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS);
}
- private void enforceSystemUserOrPermission(int userHandle, String permission) {
- if (userHandle < 0) {
- throw new IllegalArgumentException("Invalid userId " + userHandle);
- }
- final int callingUid = mInjector.binderGetCallingUid();
- if (userHandle == UserHandle.getUserId(callingUid)) {
- return;
- }
- if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+ private void enforceSystemUserOrPermission(String permission) {
+ if (!(isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID)) {
mContext.enforceCallingOrSelfPermission(permission,
"Must be system or have " + permission + " permission");
}
}
+ private void enforceSystemUserOrPermissionIfCrossUser(int userHandle, String permission) {
+ if (userHandle < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userHandle);
+ }
+ if (userHandle == mInjector.userHandleGetCallingUserId()) {
+ return;
+ }
+ enforceSystemUserOrPermission(permission);
+ }
+
private void enforceManagedProfile(int userHandle, String message) {
if(!isManagedProfile(userHandle)) {
throw new SecurityException("You can not " + message + " outside a managed profile.");
@@ -7184,6 +7243,21 @@
"Only profile owner, device owner and system may call this method.");
}
+ private void enforceProfileOwnerOrFullCrossUsersPermission(int userId) {
+ if (userId == mInjector.userHandleGetCallingUserId()) {
+ synchronized (this) {
+ if (getActiveAdminWithPolicyForUidLocked(null,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
+ != null) {
+ // Device Owner/Profile Owner may access the user it runs on.
+ return;
+ }
+ }
+ }
+ // Otherwise, INTERACT_ACROSS_USERS_FULL permission, system UID or root UID is required.
+ enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -10897,4 +10971,14 @@
}
return getUserData(userId).mDefaultInputMethodSet;
}
+
+ @Override
+ public StringParceledListSlice getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ final int userId = user.getIdentifier();
+ enforceProfileOwnerOrFullCrossUsersPermission(userId);
+ synchronized (this) {
+ return new StringParceledListSlice(
+ new ArrayList<>(getUserData(userId).mOwnerInstalledCaCerts));
+ }
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 6212626..25c29ee 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -196,6 +196,7 @@
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getNameResId(), actual.getNameResId());
assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
assertEquals(expected.getImportance(), actual.getImportance());
@@ -208,6 +209,12 @@
assertEquals(expected.getLightColor(), actual.getLightColor());
}
+ private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getNameResId(), actual.getNameResId());
+ }
+
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -260,11 +267,15 @@
@Test
public void testChannelXml() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2");
+ int nameResId = 924896;
+ int groupNameResId = 426272;
+
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", groupNameResId);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ new NotificationChannel("id2", nameResId, IMPORTANCE_LOW);
channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
@@ -275,6 +286,7 @@
channel2.setLightColor(Color.BLUE);
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
mHelper.createNotificationChannel(pkg, uid, channel2, false);
@@ -305,7 +317,9 @@
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
foundNcg = true;
- break;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
}
}
assertTrue(foundNcg);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 756514b..f797f31 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -49,6 +49,8 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
@@ -122,6 +124,34 @@
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----\n";
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -3916,6 +3946,124 @@
assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
}
+ public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
+ setDeviceOwner();
+
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
+ setAsProfileOwner(admin1);
+
+ mContext.packageName = admin1.getPackageName();
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final String delegate = "com.example.delegate";
+ final int delegateUid = setupPackageInPackageManager(delegate, 20988);
+ dpm.setCertInstallerPackage(admin1, delegate);
+
+ mContext.packageName = delegate;
+ mContext.binder.callingUid = delegateUid;
+ verifyCanGetOwnerInstalledCaCerts(null);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null);
+ }
+
+ private void verifyCanGetOwnerInstalledCaCerts(ComponentName caller) throws Exception {
+ final UserHandle user = UserHandle.getUserHandleForUid(mContext.binder.callingUid);
+ final int ownerUid = user.equals(UserHandle.SYSTEM) ?
+ DpmMockContext.CALLER_SYSTEM_USER_UID : DpmMockContext.CALLER_UID;
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Device Owner / Profile Owner can find out which CA certs were installed by itself.
+ final String packageName = mContext.packageName;
+ mContext.packageName = admin1.getPackageName();
+ final long callerIdentity = mContext.binder.clearCallingIdentity();
+ mContext.binder.callingUid = ownerUid;
+ List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertEquals(1, ownerInstalledCaCerts.size());
+ assertTrue(ownerInstalledCaCerts.contains(alias));
+
+ // Restarting the DPMS should not lose information.
+ initializeDpms();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // System can find out which CA certs were installed by the Device Owner / Profile Owner.
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // Remove the CA cert.
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ reset(mContext.keyChainConnection.getService());
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+ // Owner.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = ownerUid;
+ ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ }
+
+ private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(ComponentName caller)
+ throws Exception {
+ final UserHandle user = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Removing the Profile Owner should clear the information which CA certs were installed
+ // by it.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.clearProfileOwner(admin1);
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
@@ -3978,4 +4126,31 @@
private static StringParceledListSlice asSlice(String[] s) {
return new StringParceledListSlice(Arrays.asList(s));
}
+
+ private void flushTasks() throws Exception {
+ Boolean tasksFlushed[] = new Boolean[] {false};
+ final Runnable tasksFlushedNotifier = () -> {
+ synchronized (tasksFlushed) {
+ tasksFlushed[0] = true;
+ tasksFlushed.notify();
+ }
+ };
+
+ // Flush main thread handler.
+ dpms.mHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+
+ // Flush background thread handler.
+ tasksFlushed[0] = false;
+ dpms.mBackgroundHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 258b393..7d017c5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -316,6 +316,41 @@
public ApplicationInfo applicationInfo = null;
+ // We have to keep track of broadcast receivers registered for a given intent ourselves as the
+ // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
+ // not work.
+ private class BroadcastReceiverRegistration {
+ public final BroadcastReceiver receiver;
+ public final IntentFilter filter;
+ public final Handler scheduler;
+
+ public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
+ Handler scheduler) {
+ this.receiver = receiver;
+ this.filter = filter;
+ this.scheduler = scheduler;
+ }
+
+ public void sendBroadcastIfApplicable(int userId, Intent intent) {
+ final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
+ 0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+ 0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
+ 0 /* flags */);
+ if (filter.match(null, intent, false, "DpmMockContext") > 0) {
+ if (scheduler != null) {
+ scheduler.post(() -> {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ });
+ } else {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ }
+ }
+ }
+ }
+ private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
+
public DpmMockContext(Context context, File dataDir) {
realTestContext = context;
@@ -476,6 +511,13 @@
.thenReturn(isRunning);
}
+ public void injectBroadcast(Intent intent) {
+ final int userId = UserHandle.getUserId(binder.getCallingUid());
+ for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+ receiver.sendBroadcastIfApplicable(userId, intent);
+ }
+ }
+
@Override
public Resources getResources() {
return resources;
@@ -681,24 +723,28 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, null));
return spiedContext.registerReceiver(receiver, filter);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
spiedContext.unregisterReceiver(receiver);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
index 6c6eb7e..d665094 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -69,7 +69,7 @@
class TestParallelPackageParser extends ParallelPackageParser {
TestParallelPackageParser() {
- super(null, false, null, null);
+ super(null, false, null, null, null);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 94ff07f..4b3c2f8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -247,7 +247,7 @@
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.icon2));
- final Icon icon3 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource(
+ final Icon icon3 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.icon2));
final ShortcutInfo si1 = makeShortcut(
@@ -567,7 +567,7 @@
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
- final Icon bmp64x64_maskable = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource(
+ final Icon bmp64x64_maskable = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_64x64));
final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_512x512));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index c54fa02..900da09 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -46,7 +46,6 @@
import android.test.suitebuilder.annotation.SmallTest;
import com.android.frameworks.servicestests.R;
-import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutUser.PackageWithUser;
import java.io.File;
@@ -993,7 +992,7 @@
setCaller(CALLING_PACKAGE_1, USER_10);
- final Icon bmp32x32 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource(
+ final Icon bmp32x32 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
PersistableBundle pb = new PersistableBundle();
@@ -1047,7 +1046,7 @@
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
- | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_MASKABLE_BITMAP,
+ | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_ADAPTIVE_BITMAP,
si.getFlags());
assertNotNull(si.getBitmapPath()); // Something should be set.
assertEquals(0, si.getIconResourceId());
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 90a2ec0..fa0bd39 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -16,9 +16,10 @@
package com.android.server.pm.dex;
-import android.os.Build;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -57,6 +58,7 @@
private int mUser0;
private int mUser1;
+
@Before
public void setup() {
@@ -243,6 +245,113 @@
assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
}
+ @Test
+ public void testNotifyPackageUpdated() {
+ // Foo loads Bar main apks.
+ notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0);
+
+ // Bar is used by others now and should be in our records.
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertTrue(pui.isUsedByOtherApps());
+ assertTrue(pui.getDexUseInfoMap().isEmpty());
+
+ // Notify that bar is updated.
+ mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(),
+ mBarUser0.mPackageInfo.applicationInfo.sourceDir,
+ mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs);
+
+ // The usedByOtherApps flag should be clear now.
+ pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ }
+
+ @Test
+ public void testNotifyPackageUpdatedCodeLocations() {
+ // Simulate a split update.
+ String newSplit = mBarUser0.replaceLastSplit();
+ List<String> newSplits = new ArrayList<>();
+ newSplits.add(newSplit);
+
+ // We shouldn't find yet the new split as we didn't notify the package update.
+ notifyDexLoad(mFooUser0, newSplits, mUser0);
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNull(pui);
+
+ // Notify that bar is updated. splitSourceDirs will contain the updated path.
+ mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(),
+ mBarUser0.mPackageInfo.applicationInfo.sourceDir,
+ mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs);
+
+ // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers.
+ notifyDexLoad(mFooUser0, newSplits, mUser0);
+ pui = getPackageUseInfo(mBarUser0);
+ assertNotNull(pui);
+ assertTrue(pui.isUsedByOtherApps());
+ }
+
+ @Test
+ public void testNotifyPackageDataDestroyForOne() {
+ // Bar loads its own secondary files.
+ notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0);
+ notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1);
+
+ mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0);
+
+ // Bar should not be around since it was removed for all users.
+ PackageUseInfo pui = getPackageUseInfo(mBarUser1);
+ assertNotNull(pui);
+ assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(),
+ /*isUsedByOtherApps*/false, mUser1);
+ }
+
+ @Test
+ public void testNotifyPackageDataDestroyForeignUse() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+ // Bar loads Foo main apks.
+ notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0);
+
+ mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0);
+
+ // Foo should still be around since it's used by other apps but with no
+ // secondary dex info.
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertNotNull(pui);
+ assertTrue(pui.isUsedByOtherApps());
+ assertTrue(pui.getDexUseInfoMap().isEmpty());
+ }
+
+ @Test
+ public void testNotifyPackageDataDestroyComplete() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+ notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+ mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0);
+
+ // Foo should not be around since all its secondary dex info were deleted
+ // and it is not used by other apps.
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertNull(pui);
+ }
+
+ @Test
+ public void testNotifyPackageDataDestroyForAll() {
+ // Foo loads its own secondary files.
+ notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0);
+ notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1);
+
+ mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL);
+
+ // Bar should not be around since it was removed for all users.
+ PackageUseInfo pui = getPackageUseInfo(mBarUser0);
+ assertNull(pui);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
for (String dex : secondaries) {
@@ -317,5 +426,12 @@
}
return paths;
}
+
+ String replaceLastSplit() {
+ int length = mPackageInfo.applicationInfo.splitSourceDirs.length;
+ // Add an extra bogus dex extension to simulate a new split name.
+ mPackageInfo.applicationInfo.splitSourceDirs[length - 1] += ".dex";
+ return mPackageInfo.applicationInfo.splitSourceDirs[length - 1];
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
index 19e0bcf..2e99433 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
@@ -257,6 +257,30 @@
}
@Test
+ public void testRemovePackage() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+ assertTrue(record(mBarSecondary2User1));
+
+ // Remove the package.
+ assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
+ // Assert that we can't find the package anymore.
+ assertNull(mPackageDexUsage.getPackageUseInfo(mBarSecondary1User0.mPackageName));
+ }
+
+ @Test
+ public void testRemoveNonexistentPackage() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+
+ // Remove the package.
+ assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
+ // Remove the package again. It should return false because the package no longer
+ // has a record in the use info.
+ assertFalse(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName));
+ }
+
+ @Test
public void testRemoveUserPackage() {
// Record Bar secondaries for two different users.
assertTrue(record(mBarSecondary1User0));
@@ -282,6 +306,32 @@
assertPackageDexUsage(null, mBarSecondary2User1);
}
+ @Test
+ public void testClearUsedByOtherApps() {
+ // Write a package which is used by other apps.
+ assertTrue(record(mFooSplit2UsedByOtherApps0));
+ assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
+
+ // Check that the package is no longer used by other apps.
+ TestData noLongerUsedByOtherApps = new TestData(
+ mFooSplit2UsedByOtherApps0.mPackageName,
+ mFooSplit2UsedByOtherApps0.mDexFile,
+ mFooSplit2UsedByOtherApps0.mOwnerUserId,
+ mFooSplit2UsedByOtherApps0.mLoaderIsa,
+ /*mIsUsedByOtherApps*/false,
+ mFooSplit2UsedByOtherApps0.mPrimaryOrSplit);
+ assertPackageDexUsage(noLongerUsedByOtherApps);
+ }
+
+ @Test
+ public void testClearUsedByOtherAppsNonexistent() {
+ // Write a package which is used by other apps.
+ assertTrue(record(mFooSplit2UsedByOtherApps0));
+ assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
+ // Clearing again should return false as there should be no update on the use info.
+ assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName));
+ }
+
private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
new file mode 100644
index 0000000..1d62e01
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.CacheQuotaHint;
+import android.test.AndroidTestCase;
+import android.util.Pair;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class CacheQuotaStrategyTest extends AndroidTestCase {
+ StringWriter mWriter;
+ FastXmlSerializer mOut;
+
+ @Before
+ public void setUp() throws Exception {
+ mWriter = new StringWriter();
+ mOut = new FastXmlSerializer();
+ mOut.setOutput(mWriter);
+ }
+
+ @Test
+ public void testEmptyWrite() throws Exception {
+ CacheQuotaStrategy.saveToXml(mOut, new ArrayList<>(), 0);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"0\" />\n");
+ }
+
+ @Test
+ public void testWriteOneQuota() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint("uuid", 0, 100));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testWriteMultipleQuotas() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint("uuid", 0, 100));
+ requests.add(buildCacheQuotaHint("uuid2", 10, 250));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testNullUuidDoesntCauseCrash() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint(null, 0, 100));
+ requests.add(buildCacheQuotaHint(null, 10, 250));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testReadMultipleQuotas() throws Exception {
+ String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n";
+
+ Pair<Long, List<CacheQuotaHint>> output =
+ CacheQuotaStrategy.readFromXml(new ByteArrayInputStream(input.getBytes("UTF-8")));
+
+ assertThat(output.first).isEqualTo(1000);
+ assertThat(output.second).containsExactly(buildCacheQuotaHint("uuid", 0, 100),
+ buildCacheQuotaHint("uuid2", 10, 250));
+ }
+
+ private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) {
+ return new CacheQuotaHint.Builder()
+ .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build();
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 04e5583..2ccaefc 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -138,19 +138,18 @@
@Test
public void testReparent() throws Exception {
+ final StackWindowController stackController =
+ createStackControllerOnDisplay(sDisplayContent);
final TestTaskWindowContainerController taskController1 =
- new TestTaskWindowContainerController(
- createStackControllerOnDisplay(sDisplayContent));
+ new TestTaskWindowContainerController(stackController);
final TestAppWindowContainerController appWindowController1 = createAppWindowController(
taskController1);
final TestTaskWindowContainerController taskController2 =
- new TestTaskWindowContainerController(
- createStackControllerOnDisplay(sDisplayContent));
+ new TestTaskWindowContainerController(stackController);
final TestAppWindowContainerController appWindowController2 = createAppWindowController(
taskController2);
final TestTaskWindowContainerController taskController3 =
- new TestTaskWindowContainerController(
- createStackControllerOnDisplay(sDisplayContent));
+ new TestTaskWindowContainerController(stackController);
try {
appWindowController1.reparent(taskController1, 0);
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 73ad7c2..e589bc7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -54,7 +54,7 @@
sDisplayContent, "exiting app");
final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
exitingAppToken.mIsExiting = true;
- exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
+ exitingAppToken.getTask().mStack.mExitingAppTokens.add(exitingAppToken);
assertForAllWindowsOrder(Arrays.asList(
sWallpaperWindow,
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 5dff997..58d277b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -49,7 +49,7 @@
final ArraySet<Task> closingTasks = new ArraySet<>();
sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks);
assertEquals(1, closingTasks.size());
- assertEquals(closingWindow.mAppToken.mTask, closingTasks.valueAt(0));
+ assertEquals(closingWindow.mAppToken.getTask(), closingTasks.valueAt(0));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 8392665..18c48b7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -158,21 +158,6 @@
return win;
}
- /**
- * Creates a window for a task on a the given {@param stackId}.
- */
- private WindowState createStackWindow(int stackId, String name) {
- final StackWindowController stackController = createStackControllerOnStackOnDisplay(stackId,
- sDisplayContent);
- final TestTaskWindowContainerController taskController =
- new TestTaskWindowContainerController(stackController);
- TestAppWindowToken appWinToken = new TestAppWindowToken(sDisplayContent);
- appWinToken.mTask = taskController.mContainer;
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, name);
- win.mAppToken = appWinToken;
- return win;
- }
-
/** Asserts that the first entry is greater than the second entry. */
void assertGreaterThan(int first, int second) throws Exception {
Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index ed1530a..89e68a6 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -55,6 +55,8 @@
import com.android.server.pm.Installer.InstallerException;
import com.android.server.storage.CacheQuotaStrategy;
+import java.io.IOException;
+
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
@@ -97,7 +99,7 @@
invalidateMounts();
mHandler = new H(IoThread.get().getLooper());
- mHandler.sendEmptyMessageDelayed(H.MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+ mHandler.sendEmptyMessageDelayed(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE, DELAY_IN_MILLIS);
mStorage.registerListener(new StorageEventListener() {
@Override
@@ -343,12 +345,14 @@
private class H extends Handler {
private static final int MSG_CHECK_STORAGE_DELTA = 100;
+ private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101;
/**
* By only triggering a re-calculation after the storage has changed sizes, we can avoid
* recalculating quotas too often. Minimum change delta defines the percentage of change
* we need to see before we recalculate.
*/
private static final double MINIMUM_CHANGE_DELTA = 0.05;
+ private static final int UNSET = -1;
private static final boolean DEBUG = false;
private final StatFs mStats;
@@ -361,7 +365,6 @@
mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
mPreviousBytes = mStats.getFreeBytes();
mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
- // TODO: Load cache quotas from a file to avoid re-doing work.
}
public void handleMessage(Message msg) {
@@ -378,7 +381,26 @@
long bytesDelta = Math.abs(mPreviousBytes - mStats.getFreeBytes());
if (bytesDelta > mMinimumThresholdBytes) {
mPreviousBytes = mStats.getFreeBytes();
- recalculateQuotas();
+ recalculateQuotas(getInitializedStrategy());
+ }
+ sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+ break;
+ }
+ case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: {
+ CacheQuotaStrategy strategy = getInitializedStrategy();
+ mPreviousBytes = UNSET;
+ try {
+ mPreviousBytes = strategy.setupQuotasFromFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "An error occurred while reading the cache quota file.", e);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Cache quota XML file is malformed?", e);
+ }
+
+ // If errors occurred getting the quotas from disk, let's re-calc them.
+ if (mPreviousBytes < 0) {
+ mPreviousBytes = mStats.getFreeBytes();
+ recalculateQuotas(strategy);
}
sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
break;
@@ -391,17 +413,18 @@
}
}
- private void recalculateQuotas() {
+ private void recalculateQuotas(CacheQuotaStrategy strategy) {
if (DEBUG) {
Slog.v(TAG, ">>> recalculating quotas ");
}
+ strategy.recalculateQuotas();
+ }
+
+ private CacheQuotaStrategy getInitializedStrategy() {
UsageStatsManagerInternal usageStatsManager =
LocalServices.getService(UsageStatsManagerInternal.class);
- CacheQuotaStrategy strategy = new CacheQuotaStrategy(
- mContext, usageStatsManager, mInstaller);
- // TODO: Save cache quotas to an XML file.
- strategy.recalculateQuotas();
+ return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index bae7cdc..a923fc3 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -117,7 +117,7 @@
public UsbPortManager(Context context) {
mContext = context;
try {
- boolean ret = IServiceManager.getService("manager")
+ boolean ret = IServiceManager.getService()
.registerForNotifications("android.hardware.usb@1.0::IUsb",
"", mServiceNotification);
if (!ret) {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 3f7c908..5bb479f 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -328,8 +328,15 @@
*/
public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 0x00000080;
+ /**
+ * Indicates that the call is from a self-managed {@link ConnectionService}.
+ * <p>
+ * See also {@link Connection#PROPERTY_SELF_MANAGED}
+ */
+ public static final int PROPERTY_SELF_MANAGED = 0x00000100;
+
//******************************************************************************************
- // Next PROPERTY value: 0x00000100
+ // Next PROPERTY value: 0x00000200
//******************************************************************************************
private final String mTelecomCallId;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2e144f2..359dcb1 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -373,6 +373,24 @@
"android.telecom.INCLUDE_EXTERNAL_CALLS";
/**
+ * A boolean meta-data value indicating whether an {@link InCallService} wants to be informed of
+ * calls which have the {@link Call.Details#PROPERTY_SELF_MANAGED} property. A self-managed
+ * call is one which originates from a self-managed {@link ConnectionService} which has chosen
+ * to implement its own call user interface. An {@link InCallService} implementation which
+ * would like to be informed of external calls should set this meta-data to {@code true} in the
+ * manifest registration of their {@link InCallService}. By default, the {@link InCallService}
+ * will NOT be informed about self-managed calls.
+ * <p>
+ * An {@link InCallService} which receives self-managed calls is free to view and control the
+ * state of calls in the self-managed {@link ConnectionService}. An example use-case is
+ * exposing these calls to a wearable or automotive device via its companion app.
+ * <p>
+ * See also {@link Connection#PROPERTY_SELF_MANAGED}.
+ */
+ public static final String METADATA_INCLUDE_SELF_MANAGED_CALLS =
+ "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
+
+ /**
* The dual tone multi-frequency signaling character sent to indicate the dialing system should
* pause for a predefined period.
*/
@@ -1051,10 +1069,12 @@
/**
* Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
- * states).
+ * states) originating from either a manager or self-managed {@link ConnectionService}.
* <p>
* Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
- * </p>
+ *
+ * @return {@code true} if there is an ongoing call in either a managed or self-managed
+ * {@link ConnectionService}, {@code false} otherwise.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public boolean isInCall() {
@@ -1069,6 +1089,31 @@
}
/**
+ * Returns whether there is an ongoing call originating from a managed
+ * {@link ConnectionService}. An ongoing call can be in dialing, ringing, active or holding
+ * states.
+ * <p>
+ * If you also need to know if there are ongoing self-managed calls, use {@link #isInCall()}
+ * instead.
+ * <p>
+ * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
+ *
+ * @return {@code true} if there is an ongoing call in a managed {@link ConnectionService},
+ * {@code false} otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public boolean isInManagedCall() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling isInManagedCall().", e);
+ }
+ return false;
+ }
+
+ /**
* Returns one of the following constants that represents the current state of Telecom:
*
* {@link TelephonyManager#CALL_STATE_RINGING}
@@ -1079,6 +1124,9 @@
* {@link android.Manifest.permission#READ_PHONE_STATE} permission. This is intentional, to
* preserve the behavior of {@link TelephonyManager#getCallState()}, which also did not require
* the permission.
+ *
+ * Takes into consideration both managed and self-managed calls.
+ *
* @hide
*/
@SystemApi
@@ -1096,6 +1144,7 @@
/**
* Returns whether there currently exists is a ringing incoming-call.
*
+ * @return {@code true} if there is a managed or self-managed ringing call.
* @hide
*/
@SystemApi
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index eb1cde3..8740224 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -165,6 +165,11 @@
boolean isInCall(String callingPackage);
/**
+ * @see TelecomServiceImpl#isInManagedCall
+ */
+ boolean isInManagedCall(String callingPackage);
+
+ /**
* @see TelecomServiceImpl#isRinging
*/
boolean isRinging(String callingPackage);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 81e87b3..932c276 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4041,9 +4041,8 @@
}
}
- if (property.length() > SystemProperties.PROP_NAME_MAX
- || propVal.length() > SystemProperties.PROP_VALUE_MAX) {
- Rlog.d(TAG, "setTelephonyProperty: property to long phoneId=" + phoneId +
+ if (propVal.length() > SystemProperties.PROP_VALUE_MAX) {
+ Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
" property=" + property + " value: " + value + " propVal=" + propVal);
return;
}
@@ -4300,6 +4299,45 @@
}
/**
+ * Returns an array of Forbidden PLMNs from the USIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the caller has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @return an array of forbidden PLMNs or null if not available
+ */
+ public String[] getForbiddenPlmns() {
+ return getForbiddenPlmns(getSubId(), APPTYPE_USIM);
+ }
+
+ /**
+ * Returns an array of Forbidden PLMNs from the specified SIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @param subId subscription ID used for authentication
+ * @param appType the icc application type, like {@link #APPTYPE_USIM}
+ * @return fplmns an array of forbidden PLMNs
+ * @hide
+ */
+ public String[] getForbiddenPlmns(int subId, int appType) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null)
+ return null;
+ return telephony.getForbiddenPlmns(subId, appType);
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone starts
+ return null;
+ }
+ }
+
+ /**
* Get P-CSCF address from PCO after data connection is established or modified.
* @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
* @return array of P-CSCF address
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 88daf64..e3816b6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1278,7 +1278,6 @@
*/
void setPolicyDataEnabled(boolean enabled, int subId);
-
/**
* Get Client request stats which will contain statistical information
* on each request made by client.
@@ -1295,4 +1294,16 @@
* @hide
* */
void setSimPowerStateForSlot(int slotId, boolean powerUp);
+
+ /**
+ * Returns a list of Forbidden PLMNs from the specified SIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @param subId subscription ID used for authentication
+ * @param appType the icc application type, like {@link #APPTYPE_USIM}
+ */
+ String[] getForbiddenPlmns(int subId, int appType);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index c3b740e..eafa6b7 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -411,6 +411,8 @@
int RIL_REQUEST_GET_ACTIVITY_INFO = 135;
int RIL_REQUEST_SET_ALLOWED_CARRIERS = 136;
int RIL_REQUEST_GET_ALLOWED_CARRIERS = 137;
+ int RIL_REQUEST_SEND_DEVICE_STATE = 138;
+ int RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER = 139;
int RIL_REQUEST_SET_SIM_CARD_POWER = 140;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index d51d75e..29ba776 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -365,6 +365,12 @@
/** @hide */
@Override
+ public boolean isInstantApp(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @Override
public int getInstantAppCookieMaxSize() {
throw new UnsupportedOperationException();
}
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index b984bbf..684a101 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -36,21 +36,36 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.when;
-import android.net.ConnectivityManager;
-import android.net.NetworkCapabilities;
-
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build.VERSION_CODES;
+import android.net.ConnectivityManager.NetworkCallback;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.runner.RunWith;
+import org.junit.Before;
import org.junit.Test;
-
-
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConnectivityManagerTest {
+
+ @Mock Context mCtx;
+ @Mock IConnectivityManager mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
static NetworkCapabilities verifyNetworkCapabilities(
int legacyType, int transportType, int... capabilities) {
final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
@@ -173,4 +188,34 @@
verifyUnrestrictedNetworkCapabilities(
ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
}
+
+ @Test
+ public void testNoDoubleCallbackRegistration() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+ ApplicationInfo info = new ApplicationInfo();
+ info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+ when(mCtx.getApplicationInfo()).thenReturn(info);
+ when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+
+ manager.requestNetwork(request, callback);
+
+ // Callback is already registered, reregistration should fail.
+ Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+ expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+ }
+
+ static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+ try {
+ block.run();
+ } catch (Throwable t) {
+ if (t.getClass().equals(throwableType)) {
+ return;
+ }
+ fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+ }
+ fail("expected exception of type " + throwableType);
+ }
}
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 0e031e7..15648bd 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -309,6 +309,8 @@
CATEGORY_ATTR = 0x010103e8,
BANNER_ATTR = 0x10103f2,
ISGAME_ATTR = 0x10103f4,
+ REQUIRED_FEATURE_ATTR = 0x1010557,
+ REQUIRED_NOT_FEATURE_ATTR = 0x1010558,
};
String8 getComponentName(String8 &pkgName, String8 &componentName) {
@@ -366,11 +368,19 @@
printf("\n");
}
-static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) {
+static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1,
+ const String8& requiredFeature = String8::empty(),
+ const String8& requiredNotFeature = String8::empty()) {
printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
if (maxSdkVersion != -1) {
printf(" maxSdkVersion='%d'", maxSdkVersion);
}
+ if (requiredFeature.length() > 0) {
+ printf(" requiredFeature='%s'", requiredFeature.string());
+ }
+ if (requiredNotFeature.length() > 0) {
+ printf(" requiredNotFeature='%s'", requiredNotFeature.string());
+ }
printf("\n");
if (optional) {
@@ -1545,6 +1555,10 @@
const int32_t maxSdkVersion =
AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, -1);
+ const String8 requiredFeature = AaptXml::getAttribute(tree,
+ REQUIRED_FEATURE_ATTR, &error);
+ const String8 requiredNotFeature = AaptXml::getAttribute(tree,
+ REQUIRED_NOT_FEATURE_ATTR, &error);
if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
hasWriteExternalStoragePermission = true;
@@ -1565,7 +1579,7 @@
printUsesPermission(name,
AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
- maxSdkVersion);
+ maxSdkVersion, requiredFeature, requiredNotFeature);
} else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index ffc2de1..ec3d75e 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -76,7 +76,7 @@
<View xmlns:test="http://com.test"
attr="hey">
<Layout test:hello="hi" />
- <Layout>Some text</Layout>
+ <Layout>Some text\\</Layout>
</View>)EOF");
android::ResXMLTree tree;
@@ -128,7 +128,7 @@
ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
const char16_t* text = tree.getText(&len);
- EXPECT_EQ(StringPiece16(text, len), u"Some text");
+ EXPECT_EQ(StringPiece16(text, len), u"Some text\\");
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index f8fa80e..7210d21 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -164,6 +164,7 @@
StringBuilder& Append(const android::StringPiece& str);
const std::string& ToString() const;
const std::string& Error() const;
+ bool IsEmpty() const;
// When building StyledStrings, we need UTF-16 indices into the string,
// which is what the Java layer expects when dealing with java
@@ -185,6 +186,8 @@
inline const std::string& StringBuilder::Error() const { return error_; }
+inline bool StringBuilder::IsEmpty() const { return str_.empty(); }
+
inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
inline StringBuilder::operator bool() const { return error_.empty(); }
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index fab2f19..d9ea1bc 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -18,7 +18,6 @@
#include <expat.h>
-#include <cassert>
#include <memory>
#include <stack>
#include <string>
@@ -41,6 +40,8 @@
std::unique_ptr<xml::Node> root;
std::stack<xml::Node*> node_stack;
std::string pending_comment;
+ std::unique_ptr<xml::Text> last_text_node;
+ util::StringBuilder pending_text;
};
/**
@@ -62,6 +63,19 @@
}
}
+static void FinishPendingText(Stack* stack) {
+ if (stack->last_text_node != nullptr) {
+ if (!stack->pending_text.IsEmpty()) {
+ stack->last_text_node->text = stack->pending_text.ToString();
+ stack->pending_text = {};
+ stack->node_stack.top()->AppendChild(std::move(stack->last_text_node));
+ } else {
+ // Drop an empty text node.
+ stack->last_text_node = nullptr;
+ }
+ }
+}
+
static void AddToStack(Stack* stack, XML_Parser parser,
std::unique_ptr<Node> node) {
node->line_number = XML_GetCurrentLineNumber(parser);
@@ -83,6 +97,7 @@
const char* uri) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
if (prefix) {
@@ -99,6 +114,7 @@
static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
CHECK(!stack->node_stack.empty());
stack->node_stack.pop();
@@ -113,6 +129,7 @@
const char** attrs) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
std::unique_ptr<Element> el = util::make_unique<Element>();
SplitName(name, &el->namespace_uri, &el->name);
@@ -120,7 +137,9 @@
while (*attrs) {
Attribute attribute;
SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
- attribute.value = *attrs++;
+ util::StringBuilder builder;
+ builder.Append(*attrs++);
+ attribute.value = builder.ToString();
// Insert in sorted order.
auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
@@ -135,41 +154,38 @@
static void XMLCALL EndElementHandler(void* user_data, const char* name) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
CHECK(!stack->node_stack.empty());
// stack->nodeStack.top()->comment = std::move(stack->pendingComment);
stack->node_stack.pop();
}
-static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
- int len) {
+static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- if (!s || len <= 0) {
+ const StringPiece str(s, len);
+ if (str.empty()) {
return;
}
// See if we can just append the text to a previous text node.
- if (!stack->node_stack.empty()) {
- Node* currentParent = stack->node_stack.top();
- if (!currentParent->children.empty()) {
- Node* last_child = currentParent->children.back().get();
- if (Text* text = NodeCast<Text>(last_child)) {
- text->text.append(s, len);
- return;
- }
- }
+ if (stack->last_text_node != nullptr) {
+ stack->pending_text.Append(str);
+ return;
}
- std::unique_ptr<Text> text = util::make_unique<Text>();
- text->text.assign(s, len);
- AddToStack(stack, parser, std::move(text));
+ stack->last_text_node = util::make_unique<Text>();
+ stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
+ stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
+ stack->pending_text.Append(str);
}
static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
if (!stack->pending_comment.empty()) {
stack->pending_comment += '\n';
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index a414afe..0fc3cec6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -49,4 +49,23 @@
EXPECT_EQ(ns->namespace_prefix, "android");
}
+TEST(XmlDomTest, HandleEscapes) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
+ R"EOF(<shortcode pattern="\\d{5}">\\d{5}</shortcode>)EOF");
+
+ xml::Element* el = xml::FindRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+
+ xml::Attribute* attr = el->FindAttribute({}, "pattern");
+ ASSERT_NE(nullptr, attr);
+
+ EXPECT_EQ("\\d{5}", attr->value);
+
+ ASSERT_EQ(1u, el->children.size());
+
+ xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get());
+ ASSERT_NE(nullptr, text);
+ EXPECT_EQ("\\d{5}", text->text);
+}
+
} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index fad35d2..8130bc2 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -52,6 +52,8 @@
throw new IllegalArgumentException("Outline is not a rect shadow");
}
+ // TODO replacing the algorithm here to create better shadow
+
float shadowSize = elevationToShadow(elevation);
int saved = modifyCanvas(canvas, shadowSize);
if (saved == -1) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 0039476..2274b90 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -321,6 +321,11 @@
}
@Override
+ public boolean isInstantApp(String packageName) {
+ return false;
+ }
+
+ @Override
public int getInstantAppCookieMaxSize() {
return 0;
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
new file mode 100644
index 0000000..4f3ed60
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
new file mode 100644
index 0000000..59dbbec
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="48dp"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="48dp"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="0dp"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="100dp"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="12dp"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index b15ee95..c83d2e4 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -34,7 +34,7 @@
@SuiteClasses({
RenderTests.class, LayoutParserWrapperTest.class,
BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
- Matrix_DelegateTest.class, TestDelegates.class
+ Matrix_DelegateTest.class, TestDelegates.class, PerformanceTests.class
})
public class Main {
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index 9e60f0f..7199781 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -424,4 +424,9 @@
assertNotEquals(0, outValue.data);
assertTrue(sRenderMessages.isEmpty());
}
+
+ @Test
+ public void testRectangleShadow() throws Exception {
+ renderAndVerify("shadows_test.xml", "shadows_test.png");
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a4ab64f..a1099f8 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -376,7 +376,9 @@
/**
* Flag indicating if this network is provided by a home Passpoint provider or a roaming
- * Passpoint provider.
+ * Passpoint provider. This flag will be {@code true} if this network is provided by
+ * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+ * or is a non-Passpoint network.
*/
public boolean isHomeProviderNetwork;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index f790332..4268f24 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -236,11 +236,11 @@
public static final int TTLS = 2;
/** EAP-Password */
public static final int PWD = 3;
- /** EAP-Subscriber Identity Module */
+ /** EAP-Subscriber Identity Module [RFC-4186] */
public static final int SIM = 4;
- /** EAP-Authentication and Key Agreement */
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
public static final int AKA = 5;
- /** EAP-Authentication and Key Agreement Prime */
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
public static final int AKA_PRIME = 6;
/** Hotspot 2.0 r2 OSEN */
public static final int UNAUTH_TLS = 7;
@@ -263,11 +263,11 @@
public static final int MSCHAPV2 = 3;
/** Generic Token Card */
public static final int GTC = 4;
- /** EAP-Subscriber Identity Module */
+ /** EAP-Subscriber Identity Module [RFC-4186] */
public static final int SIM = 5;
- /** EAP-Authentication and Key Agreement */
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
public static final int AKA = 6;
- /** EAP-Authentication and Key Agreement Prime */
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
public static final int AKA_PRIME = 7;
private static final String AUTH_PREFIX = "auth=";
private static final String AUTHEAP_PREFIX = "autheap=";
@@ -756,8 +756,8 @@
* key entry when the config is saved and removing the key entry when
* the config is removed.
- * @param privateKey
- * @param clientCertificate
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificate an X509Certificate representing the end certificate.
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
@@ -775,9 +775,11 @@
* with this configuration. The framework takes care of installing the
* key entry when the config is saved and removing the key entry when
* the config is removed.
-
- * @param privateKey
- * @param clientCertificateChain
+ *
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificateChain an array of X509Certificate instances which starts with
+ * end certificate and continues with additional CA certificates necessary to
+ * link the end certificate with some root certificate known by the authenticator.
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
@@ -835,7 +837,15 @@
}
/**
- * Get the complete client certificate chain
+ * Get the complete client certificate chain in the same order as it was last supplied.
+ *
+ * <p>If the chain was last supplied by a call to
+ * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)}
+ * with a non-null * certificate instance, a single-element array containing the certificate
+ * will be * returned. If {@link #setClientKeyEntryWithCertificateChain(
+ * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a
+ * non-empty array, this array will be returned in the same order as it was supplied.
+ * Otherwise, {@code null} will be returned.
*
* @return X.509 client certificates
*/
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 620759d..2388841 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -122,12 +122,22 @@
private static final int MAX_PASSWORD_BYTES = 255;
/**
+ * Supported authentication methods.
+ * @hide
+ */
+ public static final String AUTH_METHOD_PAP = "PAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAP = "MS-CHAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAPV2 = "MS-CHAP-V2";
+
+ /**
* Supported Non-EAP inner methods. Refer to
* Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical
* Specification Section 9.1 for more info.
*/
- private static final Set<String> SUPPORTED_AUTH =
- new HashSet<String>(Arrays.asList("PAP", "CHAP", "MS-CHAP", "MS-CHAP-V2"));
+ private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(
+ Arrays.asList(AUTH_METHOD_PAP, AUTH_METHOD_MSCHAP, AUTH_METHOD_MSCHAPV2));
/**
* Username of the credential.
@@ -348,8 +358,9 @@
public static final class CertificateCredential implements Parcelable {
/**
* Supported certificate types.
+ * @hide
*/
- private static final String CERT_TYPE_X509V3 = "x509v3";
+ public static final String CERT_TYPE_X509V3 = "x509v3";
/**
* Certificate SHA-256 fingerprint length.
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index c4d2d32..d0aedba 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -89,11 +89,29 @@
@Test
public void testSetClientKeyEntryWithNull() {
mEnterpriseConfig.setClientKeyEntry(null, null);
- assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
- assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+ assertNull(mEnterpriseConfig.getClientCertificate());
mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
- assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
- assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+ assertNull(mEnterpriseConfig.getClientCertificate());
+
+ // Setting the client certificate to null should clear the existing chain.
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ X509Certificate clientCert0 = FakeKeys.CLIENT_CERT;
+ X509Certificate clientCert1 = FakeKeys.CA_CERT1;
+ mEnterpriseConfig.setClientKeyEntry(clientKey, clientCert0);
+ assertNotNull(mEnterpriseConfig.getClientCertificate());
+ mEnterpriseConfig.setClientKeyEntry(null, null);
+ assertNull(mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+
+ // Setting the chain to null should clear the existing chain.
+ X509Certificate[] clientChain = new X509Certificate[] {clientCert0, clientCert1};
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ assertNotNull(mEnterpriseConfig.getClientCertificateChain());
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+ assertNull(mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
}
@Test