Merge "Fix notification shelf icon showing on top of snooze leave behind"
diff --git a/Android.mk b/Android.mk
index 9ebc276..7a8b1b7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -264,8 +264,6 @@
core/java/android/os/storage/IObbActionListener.aidl \
core/java/android/security/IKeystoreService.aidl \
core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \
- core/java/android/service/autofill/IAutoFillAppCallback.aidl \
- core/java/android/service/autofill/IAutoFillManagerService.aidl \
core/java/android/service/autofill/IAutoFillService.aidl \
core/java/android/service/autofill/IFillCallback.aidl \
core/java/android/service/autofill/ISaveCallback.aidl \
@@ -297,9 +295,9 @@
core/java/android/printservice/IPrintService.aidl \
core/java/android/printservice/IPrintServiceClient.aidl \
core/java/android/companion/ICompanionDeviceManager.aidl \
- core/java/android/companion/ICompanionDeviceManagerService.aidl \
- core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl \
- core/java/android/companion/IOnAssociateCallback.aidl \
+ core/java/android/companion/ICompanionDeviceDiscoveryService.aidl \
+ core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl \
+ core/java/android/companion/IFindDeviceCallback.aidl \
core/java/android/service/dreams/IDreamManager.aidl \
core/java/android/service/dreams/IDreamService.aidl \
core/java/android/service/persistentdata/IPersistentDataBlockService.aidl \
@@ -318,6 +316,8 @@
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
+ core/java/android/view/autofill/IAutoFillManager.aidl \
+ core/java/android/view/autofill/IAutoFillManagerClient.aidl \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IDockedStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index 88e99e7..ad4ca3d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -121,6 +121,7 @@
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
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 RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND";
field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -139,6 +140,7 @@
field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
+ field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND";
field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
field public static final java.lang.String USE_SIP = "android.permission.USE_SIP";
field public static final java.lang.String VIBRATE = "android.permission.VIBRATE";
@@ -601,6 +603,8 @@
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+ field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -6198,6 +6202,7 @@
method public void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
+ method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6274,6 +6279,7 @@
method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isProfileOwnerApp(java.lang.String);
method public boolean isProvisioningAllowed(java.lang.String);
+ method public boolean isResetPasswordTokenActive(android.content.ComponentName);
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
@@ -6285,6 +6291,7 @@
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
+ method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6333,6 +6340,7 @@
method public void setProfileName(android.content.ComponentName, java.lang.String);
method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+ method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6566,6 +6574,7 @@
method public int getTextStyle();
method public int getTop();
method public android.graphics.Matrix getTransformation();
+ method public java.lang.String getUrl();
method public int getVisibility();
method public int getWidth();
method public boolean isAccessibilityFocused();
@@ -9182,10 +9191,6 @@
field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID";
- field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE";
- field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK";
- field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS";
- field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";
field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";
field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC";
@@ -9222,7 +9227,7 @@
field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
- field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN";
+ field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED";
field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
@@ -9927,6 +9932,15 @@
method public final int compare(android.content.pm.ApplicationInfo, android.content.pm.ApplicationInfo);
}
+ public final class ChangedPackages implements android.os.Parcelable {
+ ctor public ChangedPackages(int, java.util.List<java.lang.String>);
+ method public int describeContents();
+ method public java.util.List<java.lang.String> getPackageNames();
+ method public int getSequenceNumber();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.ChangedPackages> CREATOR;
+ }
+
public class ComponentInfo extends android.content.pm.PackageItemInfo {
ctor public ComponentInfo();
ctor public ComponentInfo(android.content.pm.ComponentInfo);
@@ -10273,6 +10287,7 @@
method public abstract java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.ChangedPackages getChangedPackages(int);
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -12381,14 +12396,14 @@
method public void rotate(float);
method public final void rotate(float, float, float);
method public int save();
- method public int save(int);
- method public int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
+ method public deprecated int save(int);
+ method public deprecated int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
method public int saveLayer(android.graphics.RectF, android.graphics.Paint);
- method public int saveLayer(float, float, float, float, android.graphics.Paint, int);
+ method public deprecated int saveLayer(float, float, float, float, android.graphics.Paint, int);
method public int saveLayer(float, float, float, float, android.graphics.Paint);
- method public int saveLayerAlpha(android.graphics.RectF, int, int);
+ method public deprecated int saveLayerAlpha(android.graphics.RectF, int, int);
method public int saveLayerAlpha(android.graphics.RectF, int);
- method public int saveLayerAlpha(float, float, float, float, int, int);
+ method public deprecated int saveLayerAlpha(float, float, float, float, int, int);
method public int saveLayerAlpha(float, float, float, float, int);
method public void scale(float, float);
method public final void scale(float, float, float, float);
@@ -12399,11 +12414,11 @@
method public void skew(float, float);
method public void translate(float, float);
field public static final int ALL_SAVE_FLAG = 31; // 0x1f
- field public static final int CLIP_SAVE_FLAG = 2; // 0x2
- field public static final int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
- field public static final int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
- field public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
- field public static final int MATRIX_SAVE_FLAG = 1; // 0x1
+ field public static final deprecated int CLIP_SAVE_FLAG = 2; // 0x2
+ field public static final deprecated int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
+ field public static final deprecated int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
+ field public static final deprecated int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
+ field public static final deprecated int MATRIX_SAVE_FLAG = 1; // 0x1
}
public static final class Canvas.EdgeType extends java.lang.Enum {
@@ -14434,6 +14449,7 @@
field public static final java.lang.String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
field public static final java.lang.String STRING_TYPE_LIGHT = "android.sensor.light";
field public static final java.lang.String STRING_TYPE_LINEAR_ACCELERATION = "android.sensor.linear_acceleration";
+ field public static final java.lang.String STRING_TYPE_LOW_LATENCY_OFFBODY_DETECT = "android.sensor.low_latency_offbody";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated";
field public static final java.lang.String STRING_TYPE_MOTION_DETECT = "android.sensor.motion_detect";
@@ -14462,6 +14478,7 @@
field public static final int TYPE_HEART_RATE = 21; // 0x15
field public static final int TYPE_LIGHT = 5; // 0x5
field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
+ field public static final int TYPE_LOW_LATENCY_OFFBODY_DETECT = 34; // 0x22
field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
field public static final int TYPE_MOTION_DETECT = 30; // 0x1e
@@ -20715,7 +20732,7 @@
method public int getStreamMaxVolume(int);
method public int getStreamVolume(int);
method public deprecated int getVibrateSetting(int);
- method public boolean isBluetoothA2dpOn();
+ method public deprecated boolean isBluetoothA2dpOn();
method public boolean isBluetoothScoAvailableOffCall();
method public boolean isBluetoothScoOn();
method public boolean isMicrophoneMute();
@@ -22306,7 +22323,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -22321,6 +22338,7 @@
method public void setAuxEffectSendLevel(float);
method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
+ method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -22333,6 +22351,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -22397,9 +22416,8 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract class MediaPlayer.OnDrmConfigCallback {
- ctor public MediaPlayer.OnDrmConfigCallback();
- method public void onDrmConfig(android.media.MediaPlayer);
+ public static abstract interface MediaPlayer.OnDrmConfigListener {
+ method public abstract void onDrmConfig(android.media.MediaPlayer);
}
public static abstract interface MediaPlayer.OnDrmInfoListener {
@@ -24086,6 +24104,7 @@
field public static final java.lang.String COLUMN_AUTHOR = "author";
field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+ field public static final java.lang.String COLUMN_BROWSABLE = "browsable";
field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis";
field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
@@ -24228,11 +24247,13 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
+ field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -36204,12 +36225,37 @@
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
- public final class FillCallback implements android.os.Parcelable {
+ public final class Dataset implements android.os.Parcelable {
method public int describeContents();
- method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.FillResponse);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.service.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.service.autofill.Dataset build();
+ method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(android.service.autofill.FillResponse);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
+ method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
public final class SaveCallback {
@@ -40040,6 +40086,7 @@
method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.ChangedPackages getChangedPackages(int);
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
@@ -44067,6 +44114,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -45617,6 +45665,7 @@
method public abstract void setTextLines(int[], int[]);
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
+ method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
@@ -46854,6 +46903,8 @@
method public void valueChanged(android.view.View);
method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, boolean);
method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
+ field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
+ field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
}
public final class AutoFillType implements android.os.Parcelable {
@@ -46881,35 +46932,6 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
}
- public final class Dataset implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
- }
-
- public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);
- method public android.view.autofill.Dataset build();
- method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
- method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
- }
-
- public final class FillResponse implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
- }
-
- public static final class FillResponse.Builder {
- ctor public FillResponse.Builder(java.lang.String);
- method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
- method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
- method public android.view.autofill.FillResponse build();
- method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
- }
-
}
package android.view.inputmethod {
@@ -52454,6 +52476,13 @@
field public static final java.lang.Class<java.lang.Boolean> TYPE;
}
+ public class BootstrapMethodError extends java.lang.LinkageError {
+ ctor public BootstrapMethodError();
+ ctor public BootstrapMethodError(java.lang.String);
+ ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+ ctor public BootstrapMethodError(java.lang.Throwable);
+ }
+
public final class Byte extends java.lang.Number implements java.lang.Comparable {
ctor public Byte(byte);
ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -54340,6 +54369,21 @@
package java.lang.invoke {
+ public abstract class CallSite {
+ method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+ method public abstract java.lang.invoke.MethodHandle getTarget();
+ method public abstract void setTarget(java.lang.invoke.MethodHandle);
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public class ConstantCallSite extends java.lang.invoke.CallSite {
+ ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+ ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public final void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class LambdaConversionException extends java.lang.Exception {
ctor public LambdaConversionException();
ctor public LambdaConversionException(java.lang.String);
@@ -54425,7 +54469,6 @@
method public java.lang.Class<?> lookupClass();
method public int lookupModes();
method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
- method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -54468,6 +54511,22 @@
method public java.lang.invoke.MethodType wrap();
}
+ public class MutableCallSite extends java.lang.invoke.CallSite {
+ ctor public MutableCallSite(java.lang.invoke.MethodType);
+ ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
+ public class VolatileCallSite extends java.lang.invoke.CallSite {
+ ctor public VolatileCallSite(java.lang.invoke.MethodType);
+ ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class WrongMethodTypeException extends java.lang.RuntimeException {
ctor public WrongMethodTypeException();
ctor public WrongMethodTypeException(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index ee6814b..02a5fa6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -211,6 +211,7 @@
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";
field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
+ field public static final java.lang.String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND";
field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
@@ -248,6 +249,7 @@
field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
+ field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND";
field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
field public static final java.lang.String USE_SIP = "android.permission.USE_SIP";
field public static final java.lang.String VIBRATE = "android.permission.VIBRATE";
@@ -713,6 +715,8 @@
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+ field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -6403,6 +6407,7 @@
method public void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
+ method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6493,6 +6498,7 @@
method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isProfileOwnerApp(java.lang.String);
method public boolean isProvisioningAllowed(java.lang.String);
+ method public boolean isResetPasswordTokenActive(android.content.ComponentName);
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
@@ -6507,6 +6513,7 @@
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
+ method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6557,6 +6564,7 @@
method public void setProfileName(android.content.ComponentName, java.lang.String);
method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+ method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6572,6 +6580,8 @@
method public void uninstallAllUserCaCerts(android.content.ComponentName);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
+ field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
+ field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
@@ -6803,6 +6813,7 @@
method public int getTextStyle();
method public int getTop();
method public android.graphics.Matrix getTransformation();
+ method public java.lang.String getUrl();
method public int getVisibility();
method public int getWidth();
method public boolean isAccessibilityFocused();
@@ -6892,6 +6903,7 @@
ctor public BackupManager(android.content.Context);
method public void backupNow();
method public android.app.backup.RestoreSession beginRestoreSession();
+ method public void cancelBackups();
method public void dataChanged();
method public static void dataChanged(java.lang.String);
method public long getAvailableRestoreToken(java.lang.String);
@@ -6908,6 +6920,7 @@
method public void setAutoRestore(boolean);
method public void setBackupEnabled(boolean);
field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
+ field public static final int ERROR_BACKUP_CANCELLED = -2003; // 0xfffff82d
field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
@@ -9620,10 +9633,6 @@
field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID";
- field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE";
- field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK";
- field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS";
- field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";
field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";
field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC";
@@ -9664,7 +9673,7 @@
field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
- field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN";
+ field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED";
field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
@@ -10373,6 +10382,15 @@
method public final int compare(android.content.pm.ApplicationInfo, android.content.pm.ApplicationInfo);
}
+ public final class ChangedPackages implements android.os.Parcelable {
+ ctor public ChangedPackages(int, java.util.List<java.lang.String>);
+ method public int describeContents();
+ method public java.util.List<java.lang.String> getPackageNames();
+ method public int getSequenceNumber();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.ChangedPackages> CREATOR;
+ }
+
public class ComponentInfo extends android.content.pm.PackageItemInfo {
ctor public ComponentInfo();
ctor public ComponentInfo(android.content.pm.ComponentInfo);
@@ -10770,6 +10788,7 @@
method public abstract java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.ChangedPackages getChangedPackages(int);
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -12970,14 +12989,14 @@
method public void rotate(float);
method public final void rotate(float, float, float);
method public int save();
- method public int save(int);
- method public int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
+ method public deprecated int save(int);
+ method public deprecated int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
method public int saveLayer(android.graphics.RectF, android.graphics.Paint);
- method public int saveLayer(float, float, float, float, android.graphics.Paint, int);
+ method public deprecated int saveLayer(float, float, float, float, android.graphics.Paint, int);
method public int saveLayer(float, float, float, float, android.graphics.Paint);
- method public int saveLayerAlpha(android.graphics.RectF, int, int);
+ method public deprecated int saveLayerAlpha(android.graphics.RectF, int, int);
method public int saveLayerAlpha(android.graphics.RectF, int);
- method public int saveLayerAlpha(float, float, float, float, int, int);
+ method public deprecated int saveLayerAlpha(float, float, float, float, int, int);
method public int saveLayerAlpha(float, float, float, float, int);
method public void scale(float, float);
method public final void scale(float, float, float, float);
@@ -12988,11 +13007,11 @@
method public void skew(float, float);
method public void translate(float, float);
field public static final int ALL_SAVE_FLAG = 31; // 0x1f
- field public static final int CLIP_SAVE_FLAG = 2; // 0x2
- field public static final int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
- field public static final int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
- field public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
- field public static final int MATRIX_SAVE_FLAG = 1; // 0x1
+ field public static final deprecated int CLIP_SAVE_FLAG = 2; // 0x2
+ field public static final deprecated int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
+ field public static final deprecated int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
+ field public static final deprecated int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
+ field public static final deprecated int MATRIX_SAVE_FLAG = 1; // 0x1
}
public static final class Canvas.EdgeType extends java.lang.Enum {
@@ -15026,6 +15045,7 @@
field public static final java.lang.String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
field public static final java.lang.String STRING_TYPE_LIGHT = "android.sensor.light";
field public static final java.lang.String STRING_TYPE_LINEAR_ACCELERATION = "android.sensor.linear_acceleration";
+ field public static final java.lang.String STRING_TYPE_LOW_LATENCY_OFFBODY_DETECT = "android.sensor.low_latency_offbody";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated";
field public static final java.lang.String STRING_TYPE_MOTION_DETECT = "android.sensor.motion_detect";
@@ -15056,6 +15076,7 @@
field public static final int TYPE_HEART_RATE = 21; // 0x15
field public static final int TYPE_LIGHT = 5; // 0x5
field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
+ field public static final int TYPE_LOW_LATENCY_OFFBODY_DETECT = 34; // 0x22
field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
field public static final int TYPE_MOTION_DETECT = 30; // 0x1e
@@ -22327,7 +22348,7 @@
method public int getStreamMaxVolume(int);
method public int getStreamVolume(int);
method public deprecated int getVibrateSetting(int);
- method public boolean isBluetoothA2dpOn();
+ method public deprecated boolean isBluetoothA2dpOn();
method public boolean isBluetoothScoAvailableOffCall();
method public boolean isBluetoothScoOn();
method public boolean isHdmiSystemAudioSupported();
@@ -23947,7 +23968,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -23962,6 +23983,7 @@
method public void setAuxEffectSendLevel(float);
method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
+ method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -23974,6 +23996,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -24038,9 +24061,8 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract class MediaPlayer.OnDrmConfigCallback {
- ctor public MediaPlayer.OnDrmConfigCallback();
- method public void onDrmConfig(android.media.MediaPlayer);
+ public static abstract interface MediaPlayer.OnDrmConfigListener {
+ method public abstract void onDrmConfig(android.media.MediaPlayer);
}
public static abstract interface MediaPlayer.OnDrmInfoListener {
@@ -25873,6 +25895,7 @@
field public static final java.lang.String COLUMN_AUTHOR = "author";
field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+ field public static final java.lang.String COLUMN_BROWSABLE = "browsable";
field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis";
field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
@@ -26096,11 +26119,13 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
+ field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -39270,12 +39295,37 @@
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
- public final class FillCallback implements android.os.Parcelable {
+ public final class Dataset implements android.os.Parcelable {
method public int describeContents();
- method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.FillResponse);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.service.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.service.autofill.Dataset build();
+ method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(android.service.autofill.FillResponse);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
+ method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
public final class SaveCallback {
@@ -43463,6 +43513,7 @@
method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.ChangedPackages getChangedPackages(int);
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -47503,6 +47554,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -49053,6 +49105,7 @@
method public abstract void setTextLines(int[], int[]);
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
+ method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
@@ -50293,6 +50346,8 @@
method public void valueChanged(android.view.View);
method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, boolean);
method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
+ field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
+ field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
}
public final class AutoFillType implements android.os.Parcelable {
@@ -50320,35 +50375,6 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
}
- public final class Dataset implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
- }
-
- public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);
- method public android.view.autofill.Dataset build();
- method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
- method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
- }
-
- public final class FillResponse implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
- }
-
- public static final class FillResponse.Builder {
- ctor public FillResponse.Builder(java.lang.String);
- method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
- method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
- method public android.view.autofill.FillResponse build();
- method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
- }
-
}
package android.view.inputmethod {
@@ -56254,6 +56280,13 @@
field public static final java.lang.Class<java.lang.Boolean> TYPE;
}
+ public class BootstrapMethodError extends java.lang.LinkageError {
+ ctor public BootstrapMethodError();
+ ctor public BootstrapMethodError(java.lang.String);
+ ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+ ctor public BootstrapMethodError(java.lang.Throwable);
+ }
+
public final class Byte extends java.lang.Number implements java.lang.Comparable {
ctor public Byte(byte);
ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -58140,6 +58173,21 @@
package java.lang.invoke {
+ public abstract class CallSite {
+ method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+ method public abstract java.lang.invoke.MethodHandle getTarget();
+ method public abstract void setTarget(java.lang.invoke.MethodHandle);
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public class ConstantCallSite extends java.lang.invoke.CallSite {
+ ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+ ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public final void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class LambdaConversionException extends java.lang.Exception {
ctor public LambdaConversionException();
ctor public LambdaConversionException(java.lang.String);
@@ -58225,7 +58273,6 @@
method public java.lang.Class<?> lookupClass();
method public int lookupModes();
method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
- method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -58268,6 +58315,22 @@
method public java.lang.invoke.MethodType wrap();
}
+ public class MutableCallSite extends java.lang.invoke.CallSite {
+ ctor public MutableCallSite(java.lang.invoke.MethodType);
+ ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
+ public class VolatileCallSite extends java.lang.invoke.CallSite {
+ ctor public VolatileCallSite(java.lang.invoke.MethodType);
+ ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class WrongMethodTypeException extends java.lang.RuntimeException {
ctor public WrongMethodTypeException();
ctor public WrongMethodTypeException(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 49b6f61..2fd0d57 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -121,6 +121,7 @@
field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
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 RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND";
field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -139,6 +140,7 @@
field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
+ field public static final java.lang.String USE_DATA_IN_BACKGROUND = "android.permission.USE_DATA_IN_BACKGROUND";
field public static final java.lang.String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
field public static final java.lang.String USE_SIP = "android.permission.USE_SIP";
field public static final java.lang.String VIBRATE = "android.permission.VIBRATE";
@@ -601,6 +603,8 @@
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+ field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -6215,6 +6219,7 @@
method public void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
+ method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6296,6 +6301,7 @@
method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isProfileOwnerApp(java.lang.String);
method public boolean isProvisioningAllowed(java.lang.String);
+ method public boolean isResetPasswordTokenActive(android.content.ComponentName);
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
@@ -6307,6 +6313,7 @@
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
+ method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6355,6 +6362,7 @@
method public void setProfileName(android.content.ComponentName, java.lang.String);
method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+ method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6370,6 +6378,8 @@
method public void uninstallAllUserCaCerts(android.content.ComponentName);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
+ field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
+ field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
@@ -6589,6 +6599,7 @@
method public int getTextStyle();
method public int getTop();
method public android.graphics.Matrix getTransformation();
+ method public java.lang.String getUrl();
method public int getVisibility();
method public int getWidth();
method public boolean isAccessibilityFocused();
@@ -9208,10 +9219,6 @@
field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID";
- field public static final java.lang.String EXTRA_AUTO_FILL_ASSIST_STRUCTURE = "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE";
- field public static final java.lang.String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK";
- field public static final java.lang.String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS";
- field public static final java.lang.String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";
field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";
field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC";
@@ -9248,7 +9255,7 @@
field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
- field public static final java.lang.String EXTRA_QUICK_VIEW_PLAIN = "android.intent.extra.QUICK_VIEW_PLAIN";
+ field public static final java.lang.String EXTRA_QUICK_VIEW_ADVANCED = "android.intent.extra.QUICK_VIEW_ADVANCED";
field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
@@ -9955,6 +9962,15 @@
method public final int compare(android.content.pm.ApplicationInfo, android.content.pm.ApplicationInfo);
}
+ public final class ChangedPackages implements android.os.Parcelable {
+ ctor public ChangedPackages(int, java.util.List<java.lang.String>);
+ method public int describeContents();
+ method public java.util.List<java.lang.String> getPackageNames();
+ method public int getSequenceNumber();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.ChangedPackages> CREATOR;
+ }
+
public class ComponentInfo extends android.content.pm.PackageItemInfo {
ctor public ComponentInfo();
ctor public ComponentInfo(android.content.pm.ComponentInfo);
@@ -10302,6 +10318,7 @@
method public abstract java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.ChangedPackages getChangedPackages(int);
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -12415,14 +12432,14 @@
method public void rotate(float);
method public final void rotate(float, float, float);
method public int save();
- method public int save(int);
- method public int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
+ method public deprecated int save(int);
+ method public deprecated int saveLayer(android.graphics.RectF, android.graphics.Paint, int);
method public int saveLayer(android.graphics.RectF, android.graphics.Paint);
- method public int saveLayer(float, float, float, float, android.graphics.Paint, int);
+ method public deprecated int saveLayer(float, float, float, float, android.graphics.Paint, int);
method public int saveLayer(float, float, float, float, android.graphics.Paint);
- method public int saveLayerAlpha(android.graphics.RectF, int, int);
+ method public deprecated int saveLayerAlpha(android.graphics.RectF, int, int);
method public int saveLayerAlpha(android.graphics.RectF, int);
- method public int saveLayerAlpha(float, float, float, float, int, int);
+ method public deprecated int saveLayerAlpha(float, float, float, float, int, int);
method public int saveLayerAlpha(float, float, float, float, int);
method public void scale(float, float);
method public final void scale(float, float, float, float);
@@ -12433,11 +12450,11 @@
method public void skew(float, float);
method public void translate(float, float);
field public static final int ALL_SAVE_FLAG = 31; // 0x1f
- field public static final int CLIP_SAVE_FLAG = 2; // 0x2
- field public static final int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
- field public static final int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
- field public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
- field public static final int MATRIX_SAVE_FLAG = 1; // 0x1
+ field public static final deprecated int CLIP_SAVE_FLAG = 2; // 0x2
+ field public static final deprecated int CLIP_TO_LAYER_SAVE_FLAG = 16; // 0x10
+ field public static final deprecated int FULL_COLOR_LAYER_SAVE_FLAG = 8; // 0x8
+ field public static final deprecated int HAS_ALPHA_LAYER_SAVE_FLAG = 4; // 0x4
+ field public static final deprecated int MATRIX_SAVE_FLAG = 1; // 0x1
}
public static final class Canvas.EdgeType extends java.lang.Enum {
@@ -14469,6 +14486,7 @@
field public static final java.lang.String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
field public static final java.lang.String STRING_TYPE_LIGHT = "android.sensor.light";
field public static final java.lang.String STRING_TYPE_LINEAR_ACCELERATION = "android.sensor.linear_acceleration";
+ field public static final java.lang.String STRING_TYPE_LOW_LATENCY_OFFBODY_DETECT = "android.sensor.low_latency_offbody";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field";
field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated";
field public static final java.lang.String STRING_TYPE_MOTION_DETECT = "android.sensor.motion_detect";
@@ -14497,6 +14515,7 @@
field public static final int TYPE_HEART_RATE = 21; // 0x15
field public static final int TYPE_LIGHT = 5; // 0x5
field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
+ field public static final int TYPE_LOW_LATENCY_OFFBODY_DETECT = 34; // 0x22
field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
field public static final int TYPE_MOTION_DETECT = 30; // 0x1e
@@ -20808,7 +20827,7 @@
method public int getStreamMaxVolume(int);
method public int getStreamVolume(int);
method public deprecated int getVibrateSetting(int);
- method public boolean isBluetoothA2dpOn();
+ method public deprecated boolean isBluetoothA2dpOn();
method public boolean isBluetoothScoAvailableOffCall();
method public boolean isBluetoothScoOn();
method public boolean isMicrophoneMute();
@@ -22399,7 +22418,7 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
- method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
@@ -22414,6 +22433,7 @@
method public void setAuxEffectSendLevel(float);
method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
+ method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -22426,6 +22446,7 @@
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
@@ -22490,9 +22511,8 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
- public static abstract class MediaPlayer.OnDrmConfigCallback {
- ctor public MediaPlayer.OnDrmConfigCallback();
- method public void onDrmConfig(android.media.MediaPlayer);
+ public static abstract interface MediaPlayer.OnDrmConfigListener {
+ method public abstract void onDrmConfig(android.media.MediaPlayer);
}
public static abstract interface MediaPlayer.OnDrmInfoListener {
@@ -24179,6 +24199,7 @@
field public static final java.lang.String COLUMN_AUTHOR = "author";
field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+ field public static final java.lang.String COLUMN_BROWSABLE = "browsable";
field public static final java.lang.String COLUMN_DURATION_MILLIS = "duration_millis";
field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final deprecated java.lang.String COLUMN_EPISODE_NUMBER = "episode_number";
@@ -24321,11 +24342,13 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
+ field public static final java.lang.String ACTION_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -36340,12 +36363,37 @@
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
- public final class FillCallback implements android.os.Parcelable {
+ public final class Dataset implements android.os.Parcelable {
method public int describeContents();
- method public void onFailure(java.lang.CharSequence);
- method public void onSuccess(android.view.autofill.FillResponse);
method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.FillCallback> CREATOR;
+ field public static final android.os.Parcelable.Creator<android.service.autofill.Dataset> CREATOR;
+ }
+
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(java.lang.CharSequence);
+ method public android.service.autofill.Dataset build();
+ method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
+ }
+
+ public final class FillCallback {
+ method public void onFailure(java.lang.CharSequence);
+ method public void onSuccess(android.service.autofill.FillResponse);
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
+ method public android.service.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
+ method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
+ method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
public final class SaveCallback {
@@ -40177,6 +40225,7 @@
method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.ChangedPackages getChangedPackages(int);
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -44374,6 +44423,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -45929,6 +45979,7 @@
method public abstract void setTextLines(int[], int[]);
method public abstract void setTextStyle(float, int, int, int);
method public abstract void setTransformation(android.graphics.Matrix);
+ method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
@@ -47168,6 +47219,8 @@
method public void valueChanged(android.view.View);
method public void virtualFocusChanged(android.view.View, int, android.graphics.Rect, boolean);
method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutoFillValue);
+ field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
+ field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
}
public final class AutoFillType implements android.os.Parcelable {
@@ -47195,35 +47248,6 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillValue> CREATOR;
}
- public final class Dataset implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.Dataset> CREATOR;
- }
-
- public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.String, java.lang.CharSequence);
- method public android.view.autofill.Dataset build();
- method public android.view.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
- method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
- }
-
- public final class FillResponse implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.view.autofill.FillResponse> CREATOR;
- }
-
- public static final class FillResponse.Builder {
- ctor public FillResponse.Builder(java.lang.String);
- method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
- method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
- method public android.view.autofill.FillResponse build();
- method public android.view.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
- method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
- }
-
}
package android.view.inputmethod {
@@ -52783,6 +52807,13 @@
field public static final java.lang.Class<java.lang.Boolean> TYPE;
}
+ public class BootstrapMethodError extends java.lang.LinkageError {
+ ctor public BootstrapMethodError();
+ ctor public BootstrapMethodError(java.lang.String);
+ ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+ ctor public BootstrapMethodError(java.lang.Throwable);
+ }
+
public final class Byte extends java.lang.Number implements java.lang.Comparable {
ctor public Byte(byte);
ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -54669,6 +54700,21 @@
package java.lang.invoke {
+ public abstract class CallSite {
+ method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+ method public abstract java.lang.invoke.MethodHandle getTarget();
+ method public abstract void setTarget(java.lang.invoke.MethodHandle);
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public class ConstantCallSite extends java.lang.invoke.CallSite {
+ ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+ ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public final void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class LambdaConversionException extends java.lang.Exception {
ctor public LambdaConversionException();
ctor public LambdaConversionException(java.lang.String);
@@ -54754,7 +54800,6 @@
method public java.lang.Class<?> lookupClass();
method public int lookupModes();
method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
- method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -54797,6 +54842,22 @@
method public java.lang.invoke.MethodType wrap();
}
+ public class MutableCallSite extends java.lang.invoke.CallSite {
+ ctor public MutableCallSite(java.lang.invoke.MethodType);
+ ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
+ public class VolatileCallSite extends java.lang.invoke.CallSite {
+ ctor public VolatileCallSite(java.lang.invoke.MethodType);
+ ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+ method public final java.lang.invoke.MethodHandle dynamicInvoker();
+ method public final java.lang.invoke.MethodHandle getTarget();
+ method public void setTarget(java.lang.invoke.MethodHandle);
+ }
+
public class WrongMethodTypeException extends java.lang.RuntimeException {
ctor public WrongMethodTypeException();
ctor public WrongMethodTypeException(java.lang.String);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index a67e47f..bfcad1b 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -127,6 +127,11 @@
return;
}
+ if ("cancel".equals(op)) {
+ doCancel();
+ return;
+ }
+
if ("whitelist".equals(op)) {
doPrintWhitelist();
return;
@@ -270,6 +275,8 @@
return "Agent error";
case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED:
return "Size quota exceeded";
+ case BackupManager.ERROR_BACKUP_CANCELLED:
+ return "Backup Cancelled";
default:
return "Unknown error";
}
@@ -361,6 +368,21 @@
}
}
+ private void doCancel() {
+ String arg = nextArg();
+ if ("backups".equals(arg)) {
+ try {
+ mBmgr.cancelBackups();
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ }
+ return;
+ }
+
+ System.err.println("Unknown command.");
+ }
+
private void doTransport() {
try {
String which = nextArg();
@@ -721,6 +743,7 @@
System.err.println(" bmgr wipe TRANSPORT PACKAGE");
System.err.println(" bmgr fullbackup PACKAGE...");
System.err.println(" bmgr backupnow --all|PACKAGE...");
+ System.err.println(" bmgr cancel backups");
System.err.println("");
System.err.println("The 'backup' command schedules a backup pass for the named package.");
System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -780,5 +803,6 @@
System.err.println("For each package it will run key/value or full data backup ");
System.err.println("depending on the package's manifest declarations.");
System.err.println("The data is sent via the currently active transport.");
+ System.err.println("The 'cancel backups' command cancels all running backups.");
}
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 7015381..b336472 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -53,6 +53,7 @@
import android.os.HandlerThread;
import android.os.IUserManager;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SELinux;
@@ -1004,7 +1005,8 @@
// In non-split user mode, userId can only be SYSTEM
int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
info = mUm.createRestrictedProfile(name, parentUserId);
- mAm.addSharedAccountsFromParentUser(parentUserId, userId);
+ mAm.addSharedAccountsFromParentUser(parentUserId, userId,
+ (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
} else if (userId < 0) {
info = mUm.createUser(name, flags);
} else {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 0263681..7d039ef 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1802,7 +1802,7 @@
public void addSharedAccountsFromParentUser(UserHandle parentUser, UserHandle user) {
try {
mService.addSharedAccountsFromParentUser(parentUser.getIdentifier(),
- user.getIdentifier());
+ user.getIdentifier(), mContext.getOpPackageName());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index e0fdac1..49cd2c6 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,7 +80,7 @@
/* Shared accounts */
Account[] getSharedAccountsAsUser(int userId);
boolean removeSharedAccountAsUser(in Account account, int userId);
- void addSharedAccountsFromParentUser(int parentUserId, int userId);
+ void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
/* Account renaming. */
void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 78cd89b..fca26f8 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1865,7 +1865,7 @@
if (mEvent == ANIMATION_START) {
return mNode.mStartTime;
} else if (mEvent == ANIMATION_DELAY_ENDED) {
- return mNode.mStartTime = mNode.mStartTime == DURATION_INFINITE
+ return mNode.mStartTime == DURATION_INFINITE
? DURATION_INFINITE : mNode.mStartTime + mNode.mAnimation.getStartDelay();
} else {
return mNode.mEndTime;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index edf60b4..4449454 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,6 +16,9 @@
package android.app;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
@@ -113,8 +116,6 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
-import android.view.autofill.AutoFillManager;
-import android.view.autofill.AutoFillSession;
import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
@@ -688,7 +689,8 @@
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
- Window.OnWindowDismissedCallback, WindowControllerCallback {
+ Window.OnWindowDismissedCallback, WindowControllerCallback,
+ AutoFillManager.AutoFillClient {
private static final String TAG = "Activity";
private static final boolean DEBUG_LIFECYCLE = false;
@@ -726,6 +728,7 @@
"android:hasCurrentPermissionsRequest";
private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
+ private static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
@@ -841,9 +844,6 @@
private boolean mHasCurrentPermissionsRequest;
- @GuardedBy("this")
- private AutoFillSession mAutoFillSession;
-
private static native String getDlWarning();
/** Return the intent that started this activity. */
@@ -1695,25 +1695,6 @@
}
/**
- * Lazily attachs the activity to the current {@link AutoFillSession} (if any).
- */
- void attachToAutoFillSession() {
- synchronized (this) {
- if (mAutoFillSession == null) {
- final AutoFillManager afm = getSystemService(AutoFillManager.class);
- if (afm != null) {
- mAutoFillSession = afm.getSession();
- if (mAutoFillSession != null) {
- mAutoFillSession.attachActivity(this);
- } else {
- Log.w(TAG, "attachToAutoFillSession(): not in a session");
- }
- }
- }
- }
- }
-
- /**
* Request the Keyboard Shortcuts screen to show up. This will trigger
* {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
*/
@@ -1799,9 +1780,8 @@
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
- if (mAutoFillSession != null && isFinishing()) {
- mAutoFillSession.finishSession();
- mAutoFillSession = null;
+ if (isFinishing() && AutoFillManager.isClientActive(getActivityToken())) {
+ getSystemService(AutoFillManager.class).reset();
}
}
@@ -6021,11 +6001,6 @@
getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
}
- if (mAutoFillSession!= null) {
- writer.print(prefix); writer.print("mAutoFillSession: " );
- writer.println(mAutoFillSession);
- }
-
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
}
@@ -6748,6 +6723,8 @@
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
+
+ AutoFillManager.addClient(token, this);
}
/** @hide */
@@ -7038,6 +7015,8 @@
return;
}
}
+ } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
+ getSystemService(AutoFillManager.class).onAuthenticationResult(data);
} else {
Fragment frag = mFragments.findFragmentByWho(who);
if (frag != null) {
@@ -7178,6 +7157,39 @@
fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
+ /** @hide */
+ @Override
+ public void autoFill(List<AutoFillId> ids, List<AutoFillValue> values) {
+ final View root = getWindow().getDecorView();
+ final int itemCount = ids.size();
+ for (int i = 0; i < itemCount; i++) {
+ final AutoFillId id = ids.get(i);
+ final AutoFillValue value = values.get(i);
+ final int viewId = id.getViewId();
+ final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ if (view == null) {
+ Log.w(TAG, "autoFill(): no View with id " + viewId);
+ continue;
+ }
+ if (id.isVirtual()) {
+ view.autoFillVirtual(id.getVirtualChildId(), value);
+ } else {
+ view.autoFill(value);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void authenticate(IntentSender intent, Intent fillInIntent) {
+ try {
+ startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
+ 0, fillInIntent, 0, 0, null);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "authenticate() failed for intent:" + intent, e);
+ }
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index efe72c3..fda9966 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -133,7 +133,7 @@
public final static boolean ENABLE_TASK_SNAPSHOTS;
static {
- ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", true);
+ ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", false);
}
static final class UidObserver extends IUidObserver.Stub {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index fa64a0f..4e345529 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -22,6 +22,7 @@
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Configuration;
+import android.net.NetworkPolicyManager.UidStateWithSeqObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.service.voice.IVoiceInteractionSession;
@@ -233,4 +234,16 @@
* @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
*/
public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+ /**
+ * Set observer which listens to uid state changes. Uid state change along with the sequence
+ * number associated with it needs to be passed to {@link UidStateWithSeqObserver}.
+ */
+ public abstract void setUidStateWithSeqObserver(UidStateWithSeqObserver observer);
+
+ /**
+ * Notifies that NetworkPolicyManagerService has updated the network policy rules for
+ * a specific {@param uid} and {@param procStateSeq}.
+ */
+ public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dffd81f..1f8e6db 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -89,8 +89,6 @@
import android.provider.Settings;
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
-import android.service.autofill.AutoFillService;
-import android.service.autofill.IAutoFillAppCallback;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -180,6 +178,7 @@
public static final boolean DEBUG_CONFIGURATION = false;
private static final boolean DEBUG_SERVICE = false;
private static final boolean DEBUG_MEMORY_TRIM = false;
+ private static final boolean DEBUG_NETWORK = false;
private static final boolean DEBUG_PROVIDER = false;
private static final boolean DEBUG_ORDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
@@ -202,6 +201,55 @@
// Whether to invoke an activity callback after delivering new configuration.
private static final boolean REPORT_TO_ACTIVITY = true;
+ /**
+ * This is the time main thread waits for the NetworkPolicyManagerService to notify
+ * that network is unrestricted. After this the app components will be launched anyway.
+ */
+ private long mWaitForNetworkTimeoutMs;
+
+ /**
+ * This is only for logging purposes. This will help us identify if the waiting for network
+ * is responsible for any lag that user might see.
+ */
+ private static final int WAIT_FOR_NETWORK_THRESHOLD_MS = 100; // 0.1 sec
+
+ /**
+ * State indicating that there is no need for any blocking for network.
+ */
+ public static final int NETWORK_STATE_NO_CHANGE = 0;
+
+ /**
+ * State indicating that main thread should wait for ActivityManagerService to notify
+ * before the app components are launched.
+ */
+ public static final int NETWORK_STATE_BLOCK = 1;
+
+ /**
+ * State indicating that any threads waiting for ActivityManagerService to notify should
+ * be unblocked.
+ */
+ public static final int NETWORK_STATE_UNBLOCK = 2;
+
+ /**
+ * Constant for indicating a invalid sequence number.
+ */
+ public static final long INVALID_PROC_STATE_SEQ = -1;
+
+ /**
+ * Current sequence number associated with the process state change.
+ */
+ @GuardedBy("mNetworkPolicyLock")
+ private long mCurProcStateSeq;
+
+ /**
+ * Indicates whether any component being launched should block for network before
+ * proceeding.
+ */
+ @GuardedBy("mNetworkPolicyLock")
+ private boolean mShouldBlockForNetwork;
+
+ private Object mNetworkPolicyLock = new Object();
+
private ContextImpl mSystemContext;
static volatile IPackageManager sPackageManager;
@@ -1306,6 +1354,18 @@
}
@Override
+ public void setBlockForNetworkState(int blockState, long targetProcStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ if (blockState == NETWORK_STATE_UNBLOCK) {
+ unblockForNetworkAccessLN(targetProcStateSeq);
+ } else if (blockState == NETWORK_STATE_BLOCK) {
+ mShouldBlockForNetwork = true;
+ }
+ mCurProcStateSeq = targetProcStateSeq;
+ }
+ }
+
+ @Override
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider);
}
@@ -1388,6 +1448,13 @@
public void handleTrustStorageUpdate() {
NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
}
+
+ @Override
+ public void notifyNetworkStateUpdated(long curProcStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ unblockForNetworkAccessLN(curProcStateSeq);
+ }
+ }
}
private int getLifecycleSeq() {
@@ -2079,6 +2146,79 @@
}
}
+ void blockForNetworkAccessInForegroundService(long procStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ if (mCurProcStateSeq >= procStateSeq) {
+ if (mShouldBlockForNetwork) {
+ blockForNetworkAccessLN();
+ }
+ } else {
+ mCurProcStateSeq = procStateSeq;
+ mShouldBlockForNetwork = true;
+ blockForNetworkAccessLN();
+ }
+ }
+ }
+
+ /**
+ * Block for unrestricted network. It will register a listener to AMS and wait for it to
+ * notify that network policy rules are updated. This method is called before relevant app
+ * components are launched.
+ */
+ private void blockForNetworkAccessLN() {
+ try {
+ if (ActivityManager.getService().registerNetworkRulesUpdateListener(
+ mAppThread, mCurProcStateSeq)) {
+ try {
+ Slog.d(TAG, "Uid: " + mBoundApplication.appInfo.uid
+ + " seq: " + mCurProcStateSeq
+ + ". Blocking for network. callers: " + Debug.getCallers(3));
+ final long blockStartTime = SystemClock.elapsedRealtime();
+ mNetworkPolicyLock.wait(mWaitForNetworkTimeoutMs);
+ final long totalWaitTime = (SystemClock.elapsedRealtime() - blockStartTime);
+ if (totalWaitTime >= mWaitForNetworkTimeoutMs) {
+ Slog.wtf(TAG, "Timed out waiting for the network rules to get updated."
+ + " Uid: " + mBoundApplication.appInfo.uid + " seq: "
+ + mCurProcStateSeq);
+ } else if (totalWaitTime >= WAIT_FOR_NETWORK_THRESHOLD_MS) {
+ Slog.d(TAG, "Waited for time greater than threshold."
+ + " Uid: " + mBoundApplication.appInfo.uid + " seq: "
+ + mCurProcStateSeq);
+ }
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG, "Uid: " + mBoundApplication.appInfo.uid
+ + " seq: " + mCurProcStateSeq
+ + ". Time waited for network: " + totalWaitTime);
+ }
+ } catch (InterruptedException ignored) {
+ }
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void checkAndBlockForNetworkAccess() {
+ synchronized (mNetworkPolicyLock) {
+ if (mWaitForNetworkTimeoutMs > 0 && mShouldBlockForNetwork) {
+ blockForNetworkAccessLN();
+ }
+ }
+ }
+
+ /**
+ * Unblock the main thread if it is waiting for network.
+ */
+ private void unblockForNetworkAccessLN(long procStateSeq) {
+ if (mShouldBlockForNetwork && procStateSeq >= mCurProcStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG, "Unblocking threads waiting for network. uid: "
+ + mBoundApplication.appInfo.uid + " procStateSeq: " + procStateSeq);
+ }
+ mNetworkPolicyLock.notifyAll();
+ mShouldBlockForNetwork = false;
+ }
+ }
+
ActivityThread() {
mResourcesManager = ResourcesManager.getInstance();
}
@@ -2671,6 +2811,7 @@
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
+ checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
@@ -2931,16 +3072,13 @@
if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) {
structure = new AssistStructure(r.activity, forAutoFill);
Intent activityIntent = r.activity.getIntent();
- boolean attachToSession = false;
// TODO(b/33197203): re-evaluate conditions below for auto-fill. In particular,
// FLAG_SECURE might be allowed on AUTO_FILL but not on AUTO_FILL_SAVE)
boolean notSecure = r.window == null ||
(r.window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_SECURE) == 0;
if (activityIntent != null && notSecure) {
- if (forAutoFill) {
- attachToSession = true;
- } else {
+ if (!forAutoFill) {
Intent intent = new Intent(activityIntent);
intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
@@ -2950,16 +3088,10 @@
} else {
if (!forAutoFill) {
content.setDefaultIntent(new Intent());
- } else {
- // activityIntent is unlikely to be null, but if it is, we should still
- // set the auto-fill callback.
- attachToSession = notSecure;
}
}
if (!forAutoFill) {
r.activity.onProvideAssistContent(content);
- } else if (attachToSession) {
- r.activity.attachToAutoFillSession();
}
}
}
@@ -5350,6 +5482,9 @@
View.mDebugViewAttributes =
mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
+ mWaitForNetworkTimeoutMs = mCoreSettings.getLong(
+ Settings.Global.WAIT_FOR_NETWORK_TIMEOUT_MS);
+
/**
* For system applications on userdebug/eng builds, log stack
* traces of disk and network access to dropbox for analysis.
@@ -5587,6 +5722,24 @@
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
+
+ // Preload fonts resources
+ try {
+ final ApplicationInfo info =
+ getPackageManager().getApplicationInfo(
+ data.appInfo.packageName,
+ PackageManager.GET_META_DATA /*flags*/,
+ UserHandle.myUserId());
+ if (info.metaData != null) {
+ final int preloadedFontsResource = info.metaData.getInt(
+ ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+ if (preloadedFontsResource != 0) {
+ data.info.mResources.preloadFonts(preloadedFontsResource);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index fb927e9..6dd31a8 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -357,7 +357,8 @@
public static final String OPSTR_INSTANT_APP_START_FOREGROUND
= "android:instant_app_start_foreground";
- private static final int[] RUNTIME_PERMISSIONS_OPS = {
+ private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
+ // RUNTIME PERMISSIONS
// Contacts
OP_READ_CONTACTS,
OP_WRITE_CONTACTS,
@@ -392,7 +393,13 @@
// Camera
OP_CAMERA,
// Body sensors
- OP_BODY_SENSORS
+ OP_BODY_SENSORS,
+
+ // APPOP PERMISSIONS
+ OP_ACCESS_NOTIFICATIONS,
+ OP_SYSTEM_ALERT_WINDOW,
+ OP_WRITE_SETTINGS,
+ OP_REQUEST_INSTALL_PACKAGES,
};
/**
@@ -926,9 +933,9 @@
AppOpsManager.MODE_ALLOWED, // OP_RUN_IN_BACKGROUND
AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
AppOpsManager.MODE_ALLOWED,
- AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
+ AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
- AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
+ AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
};
/**
@@ -1018,7 +1025,7 @@
/**
* Mapping from a permission to the corresponding app op.
*/
- private static HashMap<String, Integer> sRuntimePermToOp = new HashMap<>();
+ private static HashMap<String, Integer> sPermToOp = new HashMap<>();
static {
if (sOpToSwitch.length != _NUM_OP) {
@@ -1058,9 +1065,9 @@
sOpStrToOp.put(sOpToString[i], i);
}
}
- for (int op : RUNTIME_PERMISSIONS_OPS) {
+ for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) {
if (sOpPerms[op] != null) {
- sRuntimePermToOp.put(sOpPerms[op], op);
+ sPermToOp.put(sOpPerms[op], op);
}
}
}
@@ -1112,12 +1119,12 @@
/**
* Retrieve the app op code for a permission, or null if there is not one.
- * This API is intended to be used for mapping runtime permissions to the
- * corresponding app op.
+ * This API is intended to be used for mapping runtime or appop permissions
+ * to the corresponding app op.
* @hide
*/
public static int permissionToOpCode(String permission) {
- Integer boxedOpCode = sRuntimePermToOp.get(permission);
+ Integer boxedOpCode = sPermToOp.get(permission);
return boxedOpCode != null ? boxedOpCode : OP_NONE;
}
@@ -1462,7 +1469,7 @@
* @return The app op associated with the permission or null.
*/
public static String permissionToOp(String permission) {
- final Integer opCode = sRuntimePermToOp.get(permission);
+ final Integer opCode = sPermToOp.get(permission);
if (opCode == null) {
return null;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0c6c4ba..333e412 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -29,6 +29,7 @@
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
import android.content.pm.ComponentInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
@@ -506,6 +507,15 @@
}
@Override
+ public ChangedPackages getChangedPackages(int sequenceNumber) {
+ try {
+ return mPM.getChangedPackages(sequenceNumber, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
@SuppressWarnings("unchecked")
public FeatureInfo[] getSystemAvailableFeatures() {
try {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 585bd05..6717491 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -199,7 +199,7 @@
int getRequestedOrientation(in IBinder token);
void unbindFinished(in IBinder token, in Intent service, boolean doRebind);
void setProcessForeground(in IBinder token, int pid, boolean isForeground);
- void setServiceForeground(in ComponentName className, in IBinder token,
+ long setServiceForeground(in ComponentName className, in IBinder token,
int id, in Notification notification, int flags);
boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
void getMemoryInfo(out ActivityManager.MemoryInfo outInfo);
@@ -210,6 +210,7 @@
boolean killPids(in int[] pids, in String reason, boolean secure);
List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags);
ActivityManager.TaskThumbnail getTaskThumbnail(int taskId);
+ ActivityManager.TaskDescription getTaskDescription(int taskId);
// Retrieve running application processes in the system
List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
// Get device configuration
@@ -603,6 +604,16 @@
ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
+ /**
+ * Registers a listener for network rules state. When the network policy rules in
+ * NetworkPolicyManagerService are updated, ActivityManagerService will notify these
+ * registered listeners.
+ *
+ * @param procStateSeq The sequence number for which the listener is interested in knowing
+ * the network policy rules state.
+ * @return true if the listener is registered, false otherwise.
+ */
+ boolean registerNetworkRulesUpdateListener(IApplicationThread listener, long procStateSeq);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4fc6fb9..7378e2b 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -153,4 +153,6 @@
void handleTrustStorageUpdate();
void attachAgent(String path);
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
+ void setBlockForNetworkState(int blockState, long procStateSeq);
+ void notifyNetworkStateUpdated(long procStateSeq);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7900fc5..812daf8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3684,7 +3684,8 @@
mContext, backgroundColor);
mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(
mContext, backgroundColor);
- mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor);
+ mActionBarColor = NotificationColorUtil.resolveActionBarColor(mContext,
+ backgroundColor);
}
}
@@ -4082,16 +4083,23 @@
/**
* Construct a RemoteViews for the final heads-up notification layout.
+ *
+ * @param increasedHeight true if this layout be created with an increased height. Some
+ * styles may support showing more then just that basic 1U size
+ * and the system may decide to render important notifications
+ * slightly bigger even when collapsed.
+ *
+ * @hide
*/
- public RemoteViews createHeadsUpContentView() {
+ public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
if (mN.headsUpContentView != null
&& (mStyle == null || !mStyle.displayCustomViewInline())) {
return mN.headsUpContentView;
} else if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeHeadsUpContentView();
- if (styleView != null) {
- return styleView;
- }
+ final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
+ if (styleView != null) {
+ return styleView;
+ }
} else if (mActions.size() == 0) {
return null;
}
@@ -4100,6 +4108,13 @@
}
/**
+ * Construct a RemoteViews for the final heads-up notification layout.
+ */
+ public RemoteViews createHeadsUpContentView() {
+ return createHeadsUpContentView(false /* useIncreasedHeight */);
+ }
+
+ /**
* Construct a RemoteViews for the display in public contexts like on the lockscreen.
*
* @hide
@@ -4152,12 +4167,21 @@
mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
}
}
+ Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED);
+ mN.extras.putBoolean(EXTRA_COLORIZED, false);
+
RemoteViews header = makeNotificationHeader();
+
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;
}
@@ -4823,9 +4847,11 @@
/**
* Construct a Style-specific RemoteViews for the final HUN layout.
+ *
+ * @param increasedHeight true if this layout be created with an increased height.
* @hide
*/
- public RemoteViews makeHeadsUpContentView() {
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return null;
}
@@ -5171,6 +5197,17 @@
/**
* @hide
*/
+ @Override
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ if (increasedHeight && mBuilder.mActions.size() > 0) {
+ return makeBigContentView();
+ }
+ return super.makeHeadsUpContentView(increasedHeight);
+ }
+
+ /**
+ * @hide
+ */
public RemoteViews makeBigContentView() {
// Nasty
@@ -5578,7 +5615,10 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView() {
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+ if (increasedHeight) {
+ return makeBigContentView();
+ }
Message m = findLatestIncomingMessage();
CharSequence title = mConversationTitle != null
? mConversationTitle
@@ -6028,7 +6068,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView() {
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews expanded = makeMediaBigContentView();
return expanded != null ? expanded : makeMediaContentView();
}
@@ -6208,7 +6248,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView() {
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
return makeDecoratedHeadsUpContentView();
}
@@ -6344,7 +6384,7 @@
* @hide
*/
@Override
- public RemoteViews makeHeadsUpContentView() {
+ public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 0ae8505..a38fd43 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -46,13 +46,23 @@
*/
public class QueuedWork {
private static final String LOG_TAG = QueuedWork.class.getSimpleName();
+ private static final boolean DEBUG = true;
- /** Delay for delayed runnables */
- private static final long DELAY = 50;
+ /** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */
+ private static final long DELAY = 100;
/** Lock for this class */
private static final Object sLock = new Object();
+ /**
+ * Used to make sure that only one thread is processing work items at a time. This means that
+ * they are processed in the order added.
+ *
+ * This is separate from {@link #sLock} as this is held the whole time while work is processed
+ * and we do not want to stall the whole class.
+ */
+ private static Object sProcessingWork = new Object();
+
/** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */
@GuardedBy("sLock")
private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
@@ -78,7 +88,7 @@
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
- Process.THREAD_PRIORITY_BACKGROUND);
+ Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
@@ -125,19 +135,32 @@
* after Service command handling, etc. (so async work is never lost)
*/
public static void waitToFinish() {
+ long startTime = 0;
+ boolean hadMessages = false;
+
+ if (DEBUG) {
+ startTime = System.currentTimeMillis();
+ }
+
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
- // Force the delayed work to be processed now
+ // Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
- handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
+
+ if (DEBUG) {
+ hadMessages = true;
+ Log.d(LOG_TAG, "waiting");
+ }
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
+ processPendingWork();
+
try {
while (true) {
Runnable finisher;
@@ -155,6 +178,14 @@
} finally {
sCanDelay = true;
}
+
+ if (DEBUG) {
+ long waitTime = System.currentTimeMillis() - startTime;
+
+ if (waitTime > 0 || hadMessages) {
+ Log.d(LOG_TAG, "waited " + waitTime + " ms");
+ }
+ }
}
/**
@@ -186,6 +217,37 @@
}
}
+ private static void processPendingWork() {
+ long startTime = 0;
+
+ if (DEBUG) {
+ startTime = System.currentTimeMillis();
+ }
+
+ synchronized (sProcessingWork) {
+ LinkedList<Runnable> work;
+
+ synchronized (sLock) {
+ work = (LinkedList<Runnable>) sWork.clone();
+ sWork.clear();
+
+ // Remove all msg-s as all work will be processed now
+ getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
+ }
+
+ if (work.size() > 0) {
+ for (Runnable w : work) {
+ w.run();
+ }
+
+ if (DEBUG) {
+ Log.d(LOG_TAG, "processing " + work.size() + " items took " +
+ +(System.currentTimeMillis() - startTime) + " ms");
+ }
+ }
+ }
+ }
+
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
@@ -195,17 +257,7 @@
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
- LinkedList<Runnable> work;
-
- synchronized (sWork) {
- work = (LinkedList<Runnable>) sWork.clone();
- sWork.clear();
-
- // Remove all msg-s as all work will be processed now
- removeMessages(MSG_RUN);
- }
-
- work.forEach(Runnable::run);
+ processPendingWork();
}
}
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 4fe4f98..9cd048e 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -683,26 +683,28 @@
* flag if killing your service would be disruptive to the user, such as
* if your service is performing background music playback, so the user
* would notice if their music stopped playing.
- *
+ *
* <p>If you need your application to run on platform versions prior to API
* level 5, you can use the following model to call the the older setForeground()
* or this modern method as appropriate:
- *
+ *
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
* foreground_compatibility}
- *
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}; must not be 0.
* @param notification The Notification to be displayed.
- *
+ *
* @see #stopForeground(boolean)
*/
public final void startForeground(int id, Notification notification) {
try {
- mActivityManager.setServiceForeground(
- new ComponentName(this, mClassName), mToken, id,
- notification, 0);
+ final long procStateSeq = mActivityManager.setServiceForeground(
+ new ComponentName(this, mClassName), mToken, id, notification, 0);
+ if (procStateSeq != ActivityThread.INVALID_PROC_STATE_SEQ && mThread != null) {
+ mThread.blockForNetworkAccessInForegroundService(procStateSeq);
+ }
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 023b4f3..11ba7ee 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -53,7 +53,7 @@
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final Object CONTENT = new Object();
// Lock ordering rules:
@@ -318,6 +318,7 @@
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
+ boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
@@ -328,7 +329,8 @@
this.mapToWriteToDisk = mapToWriteToDisk;
}
- void setDiskWriteResult(boolean result) {
+ void setDiskWriteResult(boolean wasWritten, boolean result) {
+ this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
@@ -396,6 +398,8 @@
}
public void apply() {
+ final long startTime = System.currentTimeMillis();
+
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
@@ -403,6 +407,12 @@
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
+
+ if (DEBUG && mcr.wasWritten) {
+ Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ + " applied after " + (System.currentTimeMillis() - startTime)
+ + " ms");
+ }
}
};
@@ -503,13 +513,26 @@
}
public boolean commit() {
+ long startTime = 0;
+
+ if (DEBUG) {
+ startTime = System.currentTimeMillis();
+ }
+
MemoryCommitResult mcr = commitToMemory();
+
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
+ } finally {
+ if (DEBUG) {
+ Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ + " committed after " + (System.currentTimeMillis() - startTime)
+ + " ms");
+ }
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
@@ -587,10 +610,6 @@
}
}
- if (DEBUG) {
- Log.d(TAG, "queued " + mcr.memoryStateGeneration + " -> " + mFile.getName());
- }
-
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
@@ -619,8 +638,31 @@
// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
+ long startTime = 0;
+ long existsTime = 0;
+ long backupExistsTime = 0;
+ long outputStreamCreateTime = 0;
+ long writeTime = 0;
+ long fsyncTime = 0;
+ long setPermTime = 0;
+ long fstatTime = 0;
+ long deleteTime = 0;
+
+ if (DEBUG) {
+ startTime = System.currentTimeMillis();
+ }
+
+ boolean fileExists = mFile.exists();
+
+ if (DEBUG) {
+ existsTime = System.currentTimeMillis();
+
+ // Might not be set, hence init them to a default value
+ backupExistsTime = existsTime;
+ }
+
// Rename the current file so it may be used as a backup during the next read
- if (mFile.exists()) {
+ if (fileExists) {
boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
@@ -639,18 +681,21 @@
}
if (!needsWrite) {
- if (DEBUG) {
- Log.d(TAG, "skipped " + mcr.memoryStateGeneration + " -> " + mFile.getName());
- }
- mcr.setDiskWriteResult(true);
+ mcr.setDiskWriteResult(false, true);
return;
}
- if (!mBackupFile.exists()) {
+ boolean backupFileExists = mBackupFile.exists();
+
+ if (DEBUG) {
+ backupExistsTime = System.currentTimeMillis();
+ }
+
+ if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
- mcr.setDiskWriteResult(false);
+ mcr.setDiskWriteResult(false, false);
return;
}
} else {
@@ -663,19 +708,34 @@
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
+
+ if (DEBUG) {
+ outputStreamCreateTime = System.currentTimeMillis();
+ }
+
if (str == null) {
- mcr.setDiskWriteResult(false);
+ mcr.setDiskWriteResult(false, false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
+
+ if (DEBUG) {
+ writeTime = System.currentTimeMillis();
+ }
+
FileUtils.sync(str);
if (DEBUG) {
- Log.d(TAG, "wrote " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+ fsyncTime = System.currentTimeMillis();
}
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
+
+ if (DEBUG) {
+ setPermTime = System.currentTimeMillis();
+ }
+
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
@@ -685,12 +745,30 @@
} catch (ErrnoException e) {
// Do nothing
}
+
+ if (DEBUG) {
+ fstatTime = System.currentTimeMillis();
+ }
+
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
+ if (DEBUG) {
+ deleteTime = System.currentTimeMillis();
+ }
+
mDiskStateGeneration = mcr.memoryStateGeneration;
- mcr.setDiskWriteResult(true);
+ mcr.setDiskWriteResult(true, true);
+
+ Log.d(TAG, "write: " + (existsTime - startTime) + "/"
+ + (backupExistsTime - startTime) + "/"
+ + (outputStreamCreateTime - startTime) + "/"
+ + (writeTime - startTime) + "/"
+ + (fsyncTime - startTime) + "/"
+ + (setPermTime - startTime) + "/"
+ + (fstatTime - startTime) + "/"
+ + (deleteTime - startTime));
return;
} catch (XmlPullParserException e) {
@@ -698,12 +776,13 @@
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
+
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
- mcr.setDiskWriteResult(false);
+ mcr.setDiskWriteResult(false, false);
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 44db326..f330a4b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -114,7 +114,8 @@
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
-import android.service.autofill.IAutoFillManagerService;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.IAutoFillManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.service.vr.IVrManager;
@@ -130,7 +131,6 @@
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
-import android.view.autofill.AutoFillManager;
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
@@ -826,8 +826,8 @@
@Override
public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE);
- IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b);
- return new AutoFillManager(ctx, service);
+ IAutoFillManager service = IAutoFillManager.Stub.asInterface(b);
+ return new AutoFillManager(ctx.getOuterContext(), service);
}});
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f1ccabe..3b2562d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -28,6 +28,7 @@
import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.IServiceConnection;
+import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -1773,10 +1774,14 @@
* if any of the accounts have it.
* </ul>
*/
+ @SystemApi
+ @TestApi
public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED =
"android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
/** @hide See {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED} */
+ @SystemApi
+ @TestApi
public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED =
"android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
@@ -2694,6 +2699,11 @@
* Force a new device unlock password (the password needed to access the entire device, not for
* individual accounts) on the user. This takes effect immediately.
* <p>
+ * <em>For device owner and profile owners targeting SDK level
+ * {@link android.os.Build.VERSION_CODES#O} or above, this API is no longer available and will
+ * throw {@link SecurityException}. Please use the new API {@link #resetPasswordWithToken}
+ * instead. </em>
+ * <p>
* <em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
* device admins that are not device owner and not profile owner.
* The password can now only be changed if there is currently no password set. Device owner
@@ -2740,6 +2750,127 @@
}
/**
+ * Called by a profile or device owner to provision a token which can later be used to reset the
+ * device lockscreen password (if called by device owner), or work challenge (if called by
+ * profile owner), via {@link #resetPasswordWithToken}.
+ * <p>
+ * If the user currently has a lockscreen password, the provisioned token will not be
+ * immediately usable; it only becomes active after the user performs a confirm credential
+ * operation, which can be triggered by {@link KeyguardManager#createConfirmDeviceCredentialIntent}.
+ * If the user has no lockscreen password, the token is activated immediately. In all cases,
+ * the active state of the current token can be checked by {@link #isResetPasswordTokenActive}.
+ * For security reasons, un-activated tokens are only stored in memory and will be lost once
+ * the device reboots. In this case a new token needs to be provisioned again.
+ * <p>
+ * Once provisioned and activated, the token will remain effective even if the user changes
+ * or clears the lockscreen password.
+ * <p>
+ * <em>This token is highly sensitive and should be treated at the same level as user
+ * credentials. In particular, NEVER store this token on device in plaintext, especially in
+ * Device-Encrypted storage if the token will be used to reset password on FBE devices before
+ * user unlocks.
+ * </em>
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param token a secure token a least 32-byte long, which must be generated by a
+ * cryptographically strong random number generator.
+ * @return true if the operation is successful, false otherwise.
+ * @throws IllegalArgumentException if the supplied token is invalid.
+ * @throws SecurityException
+ */
+ public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
+ throwIfParentInstance("setResetPasswordToken");
+ if (mService != null) {
+ try {
+ return mService.setResetPasswordToken(admin, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by a profile or device owner to revoke the current password reset token.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return true if the operation is successful, false otherwise.
+ */
+ public boolean clearResetPasswordToken(ComponentName admin) {
+ throwIfParentInstance("clearResetPasswordToken");
+ if (mService != null) {
+ try {
+ return mService.clearResetPasswordToken(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by a profile or device owner to check if the current reset password token is active.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return true if the token is active, false otherwise.
+ * @throws IllegalStateException if no token has been set.
+ */
+ public boolean isResetPasswordTokenActive(ComponentName admin) {
+ throwIfParentInstance("isResetPasswordTokenActive");
+ if (mService != null) {
+ try {
+ return mService.isResetPasswordTokenActive(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device or profile owner to force set a new device unlock password or a work profile
+ * challenge on current user. This takes effect immediately.
+ * <p>
+ * Unlike {@link #resetPassword}, this API can change the password even before the user or
+ * device is unlocked or decrypted. The supplied token must have been previously provisioned via
+ * {@link #setResetPasswordToken}, and in active state {@link #isResetPasswordTokenActive}.
+ * <p>
+ * The given password must be sufficient for the current password quality and length constraints
+ * as returned by {@link #getPasswordQuality(ComponentName)} and
+ * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet these constraints, then
+ * it will be rejected and false returned. Note that the password may be a stronger quality
+ * (containing alphanumeric characters when the requested quality is only numeric), in which
+ * case the currently active quality will be increased to match.
+ * <p>
+ * Calling with a null or empty password will clear any existing PIN, pattern or password if the
+ * current password constraints allow it.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param password The new password for the user. Null or empty clears the password.
+ * @param token the password reset token previously provisioned by #setResetPasswordToken.
+ * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
+ * {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
+ * @return Returns true if the password was applied, or false if it is not acceptable for the
+ * current constraints.
+ * @throws SecurityException if the calling application does not own an active administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
+ * @throws IllegalStateException if the provided token is not valid.
+ * @throws IllegalArgumentException if the password does not meet system requirements.
+ */
+ public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
+ byte[] token, int flags) {
+ throwIfParentInstance("resetPassword");
+ if (mService != null) {
+ try {
+ return mService.resetPasswordWithToken(admin, password, token, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by an application that is administering the device to set the maximum time for user
* activity until the device will lock. This limits the length that the user can set. It takes
* effect immediately.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 79fe10e..c2f75c8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -342,4 +342,9 @@
long getLastSecurityLogRetrievalTime();
long getLastBugReportRequestTime();
long getLastNetworkLogRetrievalTime();
+
+ boolean setResetPasswordToken(in ComponentName admin, in byte[] token);
+ boolean clearResetPasswordToken(in ComponentName admin);
+ boolean isResetPasswordTokenActive(in ComponentName admin);
+ boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 08aa5f2..8d385db 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -17,13 +17,13 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.ViewStructure;
import android.view.ViewRootImpl;
+import android.view.ViewStructure;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
-import android.view.autofill.AutoFillId;
import java.util.ArrayList;
@@ -420,18 +420,17 @@
mRoot = new ViewNode();
ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false, 0);
- if ((root.getWindowFlags()& WindowManager.LayoutParams.FLAG_SECURE) != 0) {
- // This is a secure window, so it doesn't want a screenshot, and that
- // means we should also not copy out its view hierarchy.
-
+ if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
if (forAutoFill) {
// NOTE: flags are currently not supported, hence 0
view.onProvideAutoFillStructure(builder, 0);
} else {
+ // This is a secure window, so it doesn't want a screenshot, and that
+ // means we should also not copy out its view hierarchy for Assist
view.onProvideStructure(builder);
+ builder.setAssistBlocked(true);
+ return;
}
- builder.setAssistBlocked(true);
- return;
}
if (forAutoFill) {
// NOTE: flags are currently not supported, hence 0
@@ -580,6 +579,7 @@
static final int FLAGS_HAS_EXTRAS = 0x00400000;
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_ALL_CONTROL = 0xfff00000;
int mFlags;
@@ -588,6 +588,7 @@
CharSequence mContentDescription;
ViewNodeText mText;
+ String mUrl;
Bundle mExtras;
ViewNode[] mChildren;
@@ -652,6 +653,9 @@
if ((flags&FLAGS_HAS_TEXT) != 0) {
mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0);
}
+ if ((flags&FLAGS_HAS_URL) != 0) {
+ mUrl = in.readString();
+ }
if ((flags&FLAGS_HAS_EXTRAS) != 0) {
mExtras = in.readBundle();
}
@@ -705,6 +709,9 @@
flags |= FLAGS_HAS_COMPLEX_TEXT;
}
}
+ if (mUrl != null) {
+ flags |= FLAGS_HAS_URL;
+ }
if (mExtras != null) {
flags |= FLAGS_HAS_EXTRAS;
}
@@ -761,6 +768,9 @@
if ((flags&FLAGS_HAS_TEXT) != 0) {
mText.writeToParcel(out, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0, writeSensitive);
}
+ if ((flags&FLAGS_HAS_URL) != 0) {
+ out.writeString(mUrl);
+ }
if ((flags&FLAGS_HAS_EXTRAS) != 0) {
out.writeBundle(mExtras);
}
@@ -1041,6 +1051,20 @@
}
/**
+ * Returns the URL represented by this node.
+ *
+ * <p>Typically used in 2 categories of nodes:
+ *
+ * <ol>
+ * <li>Root node (containing the URL of the HTML page)
+ * <li>Child nodes that represent hyperlinks (contains the hyperlink URL).
+ * </ol>
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
* Returns any text associated with the node that is displayed to the user, or null
* if there is none.
*/
@@ -1487,6 +1511,11 @@
public void setSanitized(boolean sanitized) {
mNode.mSanitized = sanitized;
}
+
+ @Override
+ public void setUrl(String url) {
+ mNode.mUrl = url;
+ }
}
/** @hide */
@@ -1584,6 +1613,10 @@
Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor())
+ ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
}
+ CharSequence url = node.getUrl();
+ if (url != null) {
+ Log.i(TAG, prefix + " URL: " + url);
+ }
String hint = node.getHint();
if (hint != null) {
Log.i(TAG, prefix + " Hint: " + hint);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 59bd01f..9d02f53 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -89,6 +89,14 @@
public static final int ERROR_PACKAGE_NOT_FOUND = -2002;
/**
+ * The backup operation was cancelled.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_BACKUP_CANCELLED = -2003;
+
+ /**
* The transport for some reason was not in a good state and
* aborted the entire backup request. This is a transient
* failure and should not be retried immediately.
@@ -626,6 +634,26 @@
return -1;
}
+ /**
+ * Cancel all running backups. After this call returns, no currently running backups will
+ * interact with the selected transport.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void cancelBackups() {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.cancelBackups();
+ } catch (RemoteException e) {
+ Log.e(TAG, "cancelBackups() couldn't connect.");
+ }
+ }
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 393667d..59a941a 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -386,4 +386,10 @@
*/
int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
int flags);
+
+ /**
+ * Cancel all running backups. After this call returns, no currently running backups will
+ * interact with the selected transport.
+ */
+ void cancelBackups();
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b379c7c..c0c1a4d 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -29,12 +29,10 @@
/**
* System level service for managing companion devices
*
- * Usage:
- * To obtain an instance call
- * {@link Context#getSystemService}({@link Context#COMPANION_DEVICE_SERVICE})
- *
- * Then, call {@link #associate} to initiate the flow of associating current package
- * with a device selected by user
+ * <p>To obtain an instance call {@link Context#getSystemService}({@link
+ * Context#COMPANION_DEVICE_SERVICE}) Then, call {@link #associate(AssociationRequest,
+ * Callback, Handler)} to initiate the flow of associating current package with a
+ * device selected by user.</p>
*
* @see AssociationRequest
*/
@@ -47,6 +45,14 @@
public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
/**
+ * The package name of the companion device discovery component.
+ *
+ * @hide
+ */
+ public static final String COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME =
+ "com.android.companiondevicemanager";
+
+ /**
* A callback to receive once at least one suitable device is found, or the search failed
* (e.g. timed out)
*/
@@ -81,13 +87,20 @@
/**
* Associate this app with a companion device, selected by user
*
- * Once at least one appropriate device is found, {@code callback} will be called with a
+ * <p>Once at least one appropriate device is found, {@code callback} will be called with a
* {@link PendingIntent} that can be used to show the list of available devices for the user
* to select.
* It should be started for result (i.e. using
* {@link android.app.Activity#startIntentSenderForResult}), as the resulting
* {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected
- * device. (e.g. {@link android.bluetooth.BluetoothDevice})
+ * device. (e.g. {@link android.bluetooth.BluetoothDevice})</p>
+ *
+ * <p>If your app needs to be excluded from battery optimizations (run in the background)
+ * or to have unrestricted data access (use data in the background) you can declare that
+ * you use the {@link android.Manifest.permission#RUN_IN_BACKGROUND} and {@link
+ * android.Manifest.permission#USE_DATA_IN_BACKGROUND} respectively. Note that these
+ * special capabilities have a negative effect on the device's battery and user's data
+ * usage, therefore you should requested them when absolutely necessary.</p>
*
* @param request specific details about this request
* @param callback will be called once there's at least one device found for user to choose from
@@ -106,17 +119,16 @@
try {
mService.associate(
request,
- new IOnAssociateCallback.Stub() {
-
+ new IFindDeviceCallback.Stub() {
@Override
- public void onSuccess(PendingIntent launcher) throws RemoteException {
+ public void onSuccess(PendingIntent launcher) {
finalHandler.post(() -> {
callback.onDeviceFound(launcher.getIntentSender());
});
}
@Override
- public void onFailure(CharSequence reason) throws RemoteException {
+ public void onFailure(CharSequence reason) {
finalHandler.post(() -> callback.onFailure(reason));
}
},
diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
similarity index 71%
rename from core/java/android/companion/ICompanionDeviceManagerService.aidl
rename to core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
index ff2a7eb..4d77963 100644
--- a/core/java/android/companion/ICompanionDeviceManagerService.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
@@ -17,13 +17,14 @@
package android.companion;
import android.companion.AssociationRequest;
-import android.companion.IOnAssociateCallback;
-
+import android.companion.ICompanionDeviceDiscoveryServiceCallback;
+import android.companion.IFindDeviceCallback;
/** @hide */
-interface ICompanionDeviceManagerService {
+interface ICompanionDeviceDiscoveryService {
void startDiscovery(
in AssociationRequest request,
- in IOnAssociateCallback callback,
- in String callingPackage);
+ in String callingPackage,
+ in IFindDeviceCallback findCallback,
+ in ICompanionDeviceDiscoveryServiceCallback serviceCallback);
}
diff --git a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl
similarity index 81%
rename from core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl
rename to core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl
index c9dd345..7af708e 100644
--- a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl
@@ -16,9 +16,7 @@
package android.companion;
-import android.bluetooth.BluetoothDevice;
-
/** @hide */
-interface ICompanionDeviceManagerServiceCallback {
- void onDeviceSelected(in BluetoothDevice device);
+interface ICompanionDeviceDiscoveryServiceCallback {
+ void onDeviceSelected(String packageName, int userId);
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 065e31be..1d30ada 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -16,7 +16,7 @@
package android.companion;
-import android.companion.IOnAssociateCallback;
+import android.companion.IFindDeviceCallback;
import android.companion.AssociationRequest;
/**
@@ -26,8 +26,8 @@
*/
interface ICompanionDeviceManager {
void associate(in AssociationRequest request,
- in IOnAssociateCallback callback,
- in String callingPackage); //TODO int userId?
+ in IFindDeviceCallback callback,
+ in String callingPackage);
//TODO add these
// boolean haveNotificationAccess(String packageName);
diff --git a/core/java/android/companion/IOnAssociateCallback.aidl b/core/java/android/companion/IFindDeviceCallback.aidl
similarity index 95%
rename from core/java/android/companion/IOnAssociateCallback.aidl
rename to core/java/android/companion/IFindDeviceCallback.aidl
index 4867eadd..919e1519 100644
--- a/core/java/android/companion/IOnAssociateCallback.aidl
+++ b/core/java/android/companion/IFindDeviceCallback.aidl
@@ -19,7 +19,7 @@
import android.app.PendingIntent;
/** @hide */
-interface IOnAssociateCallback {
+interface IFindDeviceCallback {
void onSuccess(in PendingIntent launcher);
void onFailure(in CharSequence reason);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5f4c36c..b0505ac 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -669,13 +669,14 @@
* preview. {@link #getClipData} contains an optional list of content URIs
* if there is more than one item to preview. {@link #EXTRA_INDEX} is an
* optional index of the URI in the clip data to show first.
- * If {@link #EXTRA_QUICK_VIEW_PLAIN} is true, then the quick viewer should show
- * basic UI without any extra features other than quick viewing the passed items.
- * Especially, the quick viewer should not let users open the passed files
- * in other apps, which includes sharing, opening, editing, printing, etc in the
- * plain mode.
+ * <p>By default quick viewers are supposed to be lightweight and focus on
+ * previewing the content only. They should not expose features such as printing,
+ * opening in an external app, deleting, rotating, casting, etc.
+ * However, if {@link #EXTRA_QUICK_VIEW_ADVANCED} is true, then the quick viewer
+ * may show advanced UI which includes convenience actions suitable for the passed
+ * Uris.
* <p>Output: nothing.
- * @see #EXTRA_QUICK_VIEW_HIDE_DEFAULT_ACTIONS
+ * @see #EXTRA_QUICK_VIEW_ADVANCED
* @see #EXTRA_INDEX
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1806,41 +1807,6 @@
@SystemApi
public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
- /**
- * Intent extra: An id if an autofill item ({@link
- * android.view.autofill.Dataset} or {@link android.view.autofill.FillResponse}).
- * <p>
- * Type: String
- * </p>
- */
- public static final String EXTRA_AUTO_FILL_ITEM_ID = "android.intent.extra.AUTO_FILL_ITEM_ID";
-
- /**
- * Intent extra: The assist structure which captures the filled screen.
- * <p>
- * Type: {@link android.app.assist.AssistStructure}
- * </p>
- */
- public static final String EXTRA_AUTO_FILL_ASSIST_STRUCTURE =
- "android.intent.extra.AUTO_FILL_ASSIST_STRUCTURE";
-
- /**
- * Intent extra: The metadata associated with the authenticated entity ({@link
- * android.view.autofill.Dataset} or {@link android.view.autofill.FillResponse}).
- * <p>
- * Type: {@link android.os.Bundle}
- * </p>
- */
- public static final String EXTRA_AUTO_FILL_EXTRAS = "android.intent.extra.AUTO_FILL_EXTRAS";
-
- /**
- * Intent extra: A callback to report an authentication result.
- * <p>
- * Type: {@link android.view.autofill.FillResponse}
- * </p>
- */
- public static final String EXTRA_AUTO_FILL_CALLBACK = "android.intent.extra.AUTO_FILL_CALLBACK";
-
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent broadcast actions (see action variable).
@@ -4448,19 +4414,15 @@
public static final String EXTRA_INDEX = "android.intent.extra.INDEX";
/**
- * Shows a plain quick viewer UI which doesn't provide any extra features other than
- * quick viewing the items.
- *
- * <p>Especially, the quick viewer should not let users open the quick viewed files
- * in other apps, which includes sharing, opening, editing, printing, etc.
- *
- * <p>This feature is optional, and may not be handled by all quick viewers.
+ * Tells the quick viewer to show additional UI actions suitable for the passed Uris,
+ * such as opening in other apps, sharing, opening, editing, printing, deleting,
+ * casting, etc.
*
* <p>The value is boolean. By default false.
* @see ACTION_QUICK_VIEW
*/
- public static final String EXTRA_QUICK_VIEW_PLAIN =
- "android.intent.extra.QUICK_VIEW_PLAIN";
+ public static final String EXTRA_QUICK_VIEW_ADVANCED =
+ "android.intent.extra.QUICK_VIEW_ADVANCED";
/**
* Optional boolean extra indicating whether quiet mode has been switched on or off.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8465f0f..1fa4181 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -573,6 +573,11 @@
public int privateFlags;
/**
+ * @hide
+ */
+ public static final String METADATA_PRELOADED_FONTS = "preloaded_fonts";
+
+ /**
* The required smallest screen width the application can run on. If 0,
* nothing has been specified. Comes from
* {@link android.R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
diff --git a/core/java/android/view/autofill/FillResponse.aidl b/core/java/android/content/pm/ChangedPackages.aidl
similarity index 83%
copy from core/java/android/view/autofill/FillResponse.aidl
copy to core/java/android/content/pm/ChangedPackages.aidl
index b018f15..1a9f5a1 100644
--- a/core/java/android/view/autofill/FillResponse.aidl
+++ b/core/java/android/content/pm/ChangedPackages.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2016, The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.view.autofill;
+package android.content.pm;
-parcelable FillResponse;
\ No newline at end of file
+parcelable ChangedPackages;
\ No newline at end of file
diff --git a/core/java/android/content/pm/ChangedPackages.java b/core/java/android/content/pm/ChangedPackages.java
new file mode 100644
index 0000000..94b8a5d
--- /dev/null
+++ b/core/java/android/content/pm/ChangedPackages.java
@@ -0,0 +1,82 @@
+/**
+ * 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.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Packages that have been changed since the last time they
+ * were requested.
+ */
+public final class ChangedPackages implements Parcelable {
+ /** The last known sequence number for these changes */
+ private final int mSequenceNumber;
+ /** The names of the packages that have changed */
+ private final List<String> mPackageNames;
+
+ public ChangedPackages(int sequenceNumber, @NonNull List<String> packageNames) {
+ this.mSequenceNumber = sequenceNumber;
+ this.mPackageNames = packageNames;
+ }
+
+ /** @hide */
+ protected ChangedPackages(Parcel in) {
+ mSequenceNumber = in.readInt();
+ mPackageNames = in.createStringArrayList();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSequenceNumber);
+ dest.writeStringList(mPackageNames);
+ }
+
+ /**
+ * Returns the last known sequence number for these changes.
+ */
+ public int getSequenceNumber() {
+ return mSequenceNumber;
+ }
+
+ /**
+ * Returns the names of the packages that have changed.
+ */
+ public @NonNull List<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ public static final Parcelable.Creator<ChangedPackages> CREATOR =
+ new Parcelable.Creator<ChangedPackages>() {
+ public ChangedPackages createFromParcel(Parcel in) {
+ return new ChangedPackages(in);
+ }
+
+ public ChangedPackages[] newArray(int size) {
+ return new ChangedPackages[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 3fb46cf..9d36a73 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -23,6 +23,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ContainerEncryptionParams;
+import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageInstallObserver2;
@@ -611,6 +612,8 @@
String getServicesSystemSharedLibraryPackageName();
String getSharedSystemSharedLibraryPackageName();
+ ChangedPackages getChangedPackages(int sequenceNumber, int userId);
+
boolean isPackageDeviceAdminOnAnyUser(String packageName);
List<String> getPreviousCodePaths(in String packageName);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c8f6406..40deeae 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -37,6 +37,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.MaskableIconDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -803,7 +804,15 @@
}
try {
final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
- return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp);
+ if (bmp != null) {
+ BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
+ if (shortcut.hasMaskableBitmap()) {
+ return new MaskableIconDrawable(null, dr);
+ } else {
+ return dr;
+ }
+ }
+ return null;
} finally {
try {
pfd.close();
@@ -821,7 +830,8 @@
return loadDrawableResourceFromPackage(shortcut.getPackage(),
icon.getResId(), shortcut.getUserHandle(), density);
}
- case Icon.TYPE_BITMAP: {
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_BITMAP_MASKABLE: {
return icon.loadDrawable(mContext);
}
default:
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b20b5e2..308153d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@
import android.annotation.CheckResult;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -3853,6 +3854,17 @@
public abstract @NonNull String getSharedSystemSharedLibraryPackageName();
/**
+ * Returns the names of the packages that have been changed
+ * [eg. added, removed or updated] since the given sequence
+ * number.
+ * <p>If no packages have been changed, returns <code>null</code>.
+ * <p>The sequence number starts at <code>0</code> and is
+ * reset every boot.
+ */
+ public abstract @Nullable ChangedPackages getChangedPackages(
+ @IntRange(from=0) int sequenceNumber);
+
+ /**
* Get a list of features that are available on the
* system.
*
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index b4dcdf7..f1f2683 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -92,6 +92,9 @@
public static final int FLAG_IMMUTABLE = 1 << 8;
/** @hide */
+ public static final int FLAG_MASKABLE_BITMAP = 1 << 9;
+
+ /** @hide */
@IntDef(flag = true,
value = {
FLAG_DYNAMIC,
@@ -103,6 +106,7 @@
FLAG_DISABLED,
FLAG_STRINGS_RESOLVED,
FLAG_IMMUTABLE,
+ FLAG_MASKABLE_BITMAP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -690,6 +694,7 @@
switch (icon.getType()) {
case Icon.TYPE_RESOURCE:
case Icon.TYPE_BITMAP:
+ case Icon.TYPE_BITMAP_MASKABLE:
break; // OK
default:
throw getInvalidIconException();
@@ -815,8 +820,9 @@
* <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported
* and will be ignored.
*
- * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)} and
- * {@link Icon#createWithResource} are supported.
+ * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)},
+ * {@link Icon#createWithMaskableBitmap(Bitmap)}
+ * and {@link Icon#createWithResource} are supported.
* Other types, such as URI-based icons, are not supported.
*
* @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)
@@ -1442,6 +1448,15 @@
}
/**
+ * Return whether a shortcut's icon is maskable.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasMaskableBitmap() {
+ return hasFlags(FLAG_MASKABLE_BITMAP);
+ }
+
+ /**
* Return whether a shortcut only contains "key" information only or not. If true, only the
* following fields are available.
* <ul>
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 7ea62db..3f8f90e 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -64,6 +64,17 @@
private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources)
throws XmlPullParserException, IOException {
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
+ String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
+ String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
+ array.recycle();
+ if (authority != null && query != null) {
+ while (parser.next() != XmlPullParser.END_TAG) {
+ skip(parser);
+ }
+ return new FontConfig.Family(authority, query);
+ }
List<FontConfig.Font> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -84,11 +95,12 @@
int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0);
String filename = array.getString(R.styleable.FontFamilyFont_font);
+ int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0);
array.recycle();
while (parser.next() != XmlPullParser.END_TAG) {
skip(parser);
}
- return new FontConfig.Font(filename, 0, null, weight, isItalic);
+ return new FontConfig.Font(filename, 0, null, weight, isItalic, resourceId);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 04e4454..21d4b22 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -373,6 +373,20 @@
}
/**
+ * @hide
+ */
+ public void preloadFonts(@FontRes int id) {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ impl.preloadFonts(this, value, id);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
* Returns the character sequence necessary for grammatically correct pluralization
* of the given resource ID for the given quantity.
* Note that the character sequence is selected based solely on grammatical necessity,
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index bf81096..38efa49 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -32,6 +32,7 @@
import android.content.pm.ActivityInfo.Config;
import android.content.res.Configuration.NativeConfig;
import android.content.res.Resources.NotFoundException;
+import android.graphics.FontFamily;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -52,6 +53,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
/**
@@ -782,6 +784,43 @@
}
/**
+ * @hide
+ */
+ public void preloadFonts(Resources wrapper, TypedValue value, int id) {
+ if (value.string == null) {
+ throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") is not a Font: " + value);
+ }
+
+ final String file = value.string.toString();
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "font");
+ final FontConfig config = FontResourcesParser.parse(rp, wrapper);
+ final List<FontConfig.Family> families = config.getFamilies();
+ if (families == null || families.isEmpty()) {
+ return;
+ }
+ for (int j = 0; j < families.size(); j++) {
+ final FontConfig.Family family = families.get(j);
+ final List<FontConfig.Font> fonts = family.getFonts();
+ for (int i = 0; i < fonts.size(); i++) {
+ int resourceId = fonts.get(i).getResourceId();
+ wrapper.getFont(resourceId);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse xml resource " + file, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read xml resource " + file, e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+
+ /**
* Given the value and id, we can get the XML filename as in value.data, based on that, we
* first try to load CSL from the cache. If not found, try to get from the constant state.
* Last, parse the XML and generate the CSL.
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index d87c55e..8c13cc8 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -649,9 +649,22 @@
* to be in the same order as the HAL. Skipping this sensor
*/
- /* TYPE_LOW_LATENCY_OFF_BODY_SENSOR - defined as type 34 in the HAL needs to
- * be defined in this space.
+ /**
+ * A constant describing a low latency off-body detect sensor.
+ *
+ * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
+ *
*/
+ public static final int TYPE_LOW_LATENCY_OFFBODY_DETECT = 34;
+
+
+ /**
+ * A constant string describing a low-latency offbody detector sensor.
+ *
+ * @see #TYPE_LOW_LATENCY_OFFBODY_DETECT
+ */
+ public static final String STRING_TYPE_LOW_LATENCY_OFFBODY_DETECT =
+ "android.sensor.low_latency_offbody";
/**
* A constant describing an uncalibrated accelerometer sensor.
@@ -669,6 +682,7 @@
*/
public static final String STRING_TYPE_ACCELEROMETER_UNCALIBRATED =
"android.sensor.accelerometer_uncalibrated";
+
/**
* A constant describing all sensor types.
*/
@@ -786,7 +800,7 @@
1, // SENSOR_TYPE_HEART_BEAT
2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
16,// skip over additional sensor info type
- 1, // reserving for LLOB sensor type
+ 1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT
6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
};
@@ -1195,6 +1209,9 @@
case TYPE_DYNAMIC_SENSOR_META:
mStringType = STRING_TYPE_DYNAMIC_SENSOR_META;
return true;
+ case TYPE_LOW_LATENCY_OFFBODY_DETECT:
+ mStringType = STRING_TYPE_LOW_LATENCY_OFFBODY_DETECT;
+ return true;
case TYPE_ACCELEROMETER_UNCALIBRATED:
mStringType = STRING_TYPE_ACCELEROMETER_UNCALIBRATED;
return true;
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 9b72757a..c0bca97 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -564,6 +564,42 @@
* completely unlikely to be anywhere else on the QRS complex.
* </p>
*
+ * <h4>{@link android.hardware.Sensor#TYPE_LOW_LATENCY_OFFBODY_DETECT
+ * Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT}:</h4>
+ *
+ * <p>
+ * A sensor of this type returns an event every time the device transitions
+ * from off-body to on-body and from on-body to off-body (e.g. a wearable
+ * device being removed from the wrist would trigger an event indicating an
+ * off-body transition). The event returned will contain a single value to
+ * indicate off-body state:
+ * </p>
+ *
+ * <ul>
+ * <li> values[0]: off-body state</li>
+ * </ul>
+ *
+ * <p>
+ * Valid values for off-body state:
+ * <ul>
+ * <li> 1.0 (device is on-body)</li>
+ * <li> 0.0 (device is off-body)</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * When a sensor of this type is activated, it must deliver the initial
+ * on-body or off-body event representing the current device state within
+ * 5 seconds of activating the sensor.
+ * </p>
+ *
+ * <p>
+ * This sensor must be able to detect and report an on-body to off-body
+ * transition within 1 second of the device being removed from the body,
+ * and must be able to detect and report an off-body to on-body transition
+ * within 5 seconds of the device being put back onto the body.
+ * </p>
+ *
* <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER_UNCALIBRATED
* Sensor.TYPE_ACCELEROMETER_UNCALIBRATED}:</h4> All values are in SI
* units (m/s^2)
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index bcebb7d..dcd069d 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -250,6 +250,10 @@
* application must then call {@link OutputConfiguration#addSurface} before finalizing the
* configuration with this method.</p>
*
+ * <p>If the provided OutputConfigurations are unchanged from session creation, this function
+ * call has no effect. This function must only be called once for a particular output
+ * configuration. </p>
+ *
* <p>The output Surfaces included by this list of
* {@link OutputConfiguration OutputConfigurations} can be used as {@link CaptureRequest}
* targets as soon as this call returns.</p>
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index dc5750d..719a957 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1453,8 +1453,9 @@
private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
if (delay >= 0) {
Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
- Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
- sCallbackHandler.sendMessageDelayed(msg, delay);
+ CallbackHandler handler = getHandler();
+ Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ handler.sendMessageDelayed(msg, delay);
}
}
@@ -2897,19 +2898,19 @@
}
}
- static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
- static CallbackHandler sCallbackHandler;
+ private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+ private static CallbackHandler sCallbackHandler;
- private final static int LISTEN = 1;
- private final static int REQUEST = 2;
+ private static final int LISTEN = 1;
+ private static final int REQUEST = 2;
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
NetworkCallback callback, int timeoutMs, int action, int legacyType) {
- return sendRequestForNetwork(need, callback, getHandler(), timeoutMs, action, legacyType);
+ return sendRequestForNetwork(need, callback, timeoutMs, action, legacyType, getHandler());
}
- private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
- NetworkCallback callback, Handler handler, int timeoutMs, int action, int legacyType) {
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+ int timeoutMs, int action, int legacyType, CallbackHandler handler) {
if (callback == null) {
throw new IllegalArgumentException("null NetworkCallback");
}
@@ -2997,8 +2998,8 @@
*
* This function behaves identically to the non-timedout version, but if a suitable
* network is not found within the given time (in milliseconds) the
- * {@link NetworkCallback#unavailable} callback is called. The request must
- * still be released normally by calling {@link unregisterNetworkCallback(NetworkCallback)}.
+ * {@link NetworkCallback#onUnavailable()} callback is called. The request must
+ * still be released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -3010,10 +3011,7 @@
* the callbacks must not be shared - they uniquely specify
* this request.
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
- * before {@link NetworkCallback#unavailable} is called.
- *
- * TODO: Make timeouts work and then unhide this method.
- *
+ * before {@link NetworkCallback#onUnavailable()} is called.
* @hide
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
@@ -3023,13 +3021,6 @@
}
/**
- * The maximum number of milliseconds the framework will look for a suitable network
- * during a timeout-equiped call to {@link requestNetwork}.
- * {@hide}
- */
- public final static int MAX_NETWORK_REQUEST_TIMEOUT_MS = 100 * 60 * 1000;
-
- /**
* The lookup key for a {@link Network} object included with the intent after
* successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index e5f0bf0..31a74dc 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
import java.util.Objects;
@@ -41,6 +42,8 @@
// etc.) so that clients can pull out these details depending on the type of network.
public class NetworkKey implements Parcelable {
+ private static final String TAG = "NetworkKey";
+
/** A wifi network, for which {@link #wifiKey} will be populated. */
public static final int TYPE_WIFI = 1;
@@ -59,13 +62,28 @@
/**
* Constructs a new NetworkKey for the given wifi {@link ScanResult}.
*
- * @throws IllegalArgumentException if the given ScanResult is malformed
+ * @return A new {@link NetworkKey} instance or <code>null</code> if the given
+ * {@link ScanResult} instance is malformed.
* @hide
*/
- public static NetworkKey createFromScanResult(ScanResult result) {
- return new NetworkKey(
- new WifiKey(
- '"' + result.wifiSsid.toString() + '"', result.BSSID));
+ @Nullable
+ public static NetworkKey createFromScanResult(@Nullable ScanResult result) {
+ if (result != null && result.wifiSsid != null) {
+ final String ssid = result.wifiSsid.toString();
+ final String bssid = result.BSSID;
+ if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+ && !TextUtils.isEmpty(bssid)) {
+ WifiKey wifiKey;
+ try {
+ wifiKey = new WifiKey(String.format("\"%s\"", ssid), bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to create WifiKey.", e);
+ return null;
+ }
+ return new NetworkKey(wifiKey);
+ }
+ }
+ return null;
}
/**
@@ -83,7 +101,14 @@
final String bssid = wifiInfo.getBSSID();
if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
&& !TextUtils.isEmpty(bssid)) {
- return new NetworkKey(new WifiKey(ssid, bssid));
+ WifiKey wifiKey;
+ try {
+ wifiKey = new WifiKey(ssid, bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to create WifiKey.", e);
+ return null;
+ }
+ return new NetworkKey(wifiKey);
}
}
return null;
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 1b715af..4b184f1 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.GET_SIGNATURES;
import static android.net.NetworkPolicy.CYCLE_NONE;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -65,6 +66,8 @@
*
* See network-policy-restrictions.md for more info.
*/
+ /** Not a valid rule */
+ public static final int RULE_INVALID = -1;
/** No specific rule was set */
public static final int RULE_NONE = 0;
/** Allow traffic on metered networks. */
@@ -360,6 +363,8 @@
final StringBuilder string = new StringBuilder().append(uidRules).append(" (");
if (uidRules == RULE_NONE) {
string.append("NONE");
+ } else if (uidRules == RULE_INVALID) {
+ string.append("INVALID");
} else {
string.append(DebugUtils.flagsToString(NetworkPolicyManager.class, "RULE_", uidRules));
}
@@ -381,4 +386,25 @@
string.append(")");
return string.toString();
}
+
+ /**
+ * @hide
+ */
+ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
+ return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isProcStateAllowedWhileRestrictBackgroundOn(int procState) {
+ return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
+ /**
+ * @hide
+ */
+ public interface UidStateWithSeqObserver {
+ void onUidStateChangedWithSeq(int uid, int procState, long seq);
+ }
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index fa9f394..b3366d8 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -506,4 +506,24 @@
state.writer.flush();
}
}
+
+ /**
+ * Instructs the zygote to preload the default set of classes and resources. Returns
+ * {@code true} if a preload was performed as a result of this call, and {@code false}
+ * otherwise. The latter usually means that the zygote eagerly preloaded at startup
+ * or due to a previous call to {@code preloadDefault}. Note that this call is synchronous.
+ */
+ public boolean preloadDefault(String abi) throws ZygoteStartFailedEx, IOException {
+ synchronized (mLock) {
+ ZygoteState state = openZygoteSocketIfNeeded(abi);
+ // Each query starts with the argument count (1 in this case)
+ state.writer.write("1");
+ state.writer.newLine();
+ state.writer.write("--preload-default");
+ state.writer.newLine();
+ state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
+ }
+ }
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 11b9606..ee8eed1 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -348,8 +348,8 @@
if (msg.what == UPDATE_SLIDER) {
if (mSeekBar != null) {
mLastProgress = msg.arg1;
- mLastAudibleStreamVolume = Math.abs(msg.arg2);
- final boolean muted = msg.arg2 < 0;
+ mLastAudibleStreamVolume = msg.arg2;
+ final boolean muted = ((Boolean)msg.obj).booleanValue();
if (muted != mMuted) {
mMuted = muted;
if (mCallback != null) {
@@ -362,8 +362,7 @@
}
public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
- final int arg2 = lastAudibleVolume * (mute ? -1 : 1);
- obtainMessage(UPDATE_SLIDER, volume, arg2).sendToTarget();
+ obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1655847..9ecc6389 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -844,6 +844,22 @@
"android.settings.SYSTEM_UPDATE_SETTINGS";
/**
+ * Activity Action: Show settings for managed profile settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGED_PROFILE_SETTINGS =
+ "android.settings.MANAGED_PROFILE_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of sync settings.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -6612,6 +6628,14 @@
"lock_screen_show_notifications";
/**
+ * This preference stores the last stack active task time for each user, which affects what
+ * tasks will be visible in Overview.
+ * @hide
+ */
+ public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME =
+ "overview_last_stack_active_time";
+
+ /**
* List of TV inputs that are currently hidden. This is a string
* containing the IDs of all hidden TV inputs. Each ID is encoded by
* {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -9557,6 +9581,16 @@
public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
/**
+ * When blocked for the network policy rules to get updated, the maximum time that the
+ * {@link ActivityThread} have to wait before unblocking.
+ *
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String WAIT_FOR_NETWORK_TIMEOUT_MS = "wait_for_network_timeout_ms";
+
+ /**
* The reason for the settings database being downgraded. This is only for
* troubleshooting purposes and its value should not be interpreted in any way.
*
diff --git a/core/java/android/provider/SettingsStringUtil.java b/core/java/android/provider/SettingsStringUtil.java
index f242d79..3dfedea 100644
--- a/core/java/android/provider/SettingsStringUtil.java
+++ b/core/java/android/provider/SettingsStringUtil.java
@@ -60,7 +60,7 @@
StringBuilder sb = new StringBuilder();
Iterator<T> it = iterator();
if (it.hasNext()) {
- sb.append(it.next());
+ sb.append(itemToString(it.next()));
while (it.hasNext()) {
sb.append(DELIMITER);
sb.append(itemToString(it.next()));
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 6da6a39..4099f59 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -30,7 +30,6 @@
import android.os.ICancellationSignal;
import android.os.Looper;
import android.util.Log;
-import android.view.autofill.FillResponse;
import com.android.internal.os.SomeArgs;
@@ -70,7 +69,7 @@
// Internal extras
/** @hide */
public static final String EXTRA_ACTIVITY_TOKEN =
- "android.service.autofill.EXTRA_ACTIVITY_TOKEN";
+ "android.service.autofill.extra.ACTIVITY_TOKEN";
// Handler messages.
private static final int MSG_CONNECT = 1;
@@ -180,15 +179,16 @@
* service.
*
* <p>Service must call one of the {@link FillCallback} methods (like
- * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)})
+ * {@link FillCallback#onSuccess(FillResponse)}
+ * or {@link FillCallback#onFailure(CharSequence)})
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
* @param data bundle containing data passed by the service on previous calls to fill.
* This bundle allows your service to keep state between fill and save requests
* as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link FillResponse}
- * Javadoc for examples of multiple-sections requests.
+ * aggressively unbind from the service to conserve resources. See {@link
+ * FillResponse} Javadoc for examples of multiple-sections requests.
* @param cancellationSignal signal for observing cancellation requests. The system will use
* this to notify you that the fill result is no longer needed and you should stop
* handling this fill request in order to save resources.
@@ -208,8 +208,8 @@
* @param data bundle containing data passed by the service on previous calls to fill.
* This bundle allows your service to keep state between fill and save requests
* as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link FillResponse}
- * Javadoc for examples of multiple-sections requests.
+ * aggressively unbind from the service to conserve resources. See {@link
+ * FillResponse} Javadoc for examples of multiple-sections requests.
* @param callback object used to notify the result of the request.
*/
public abstract void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
diff --git a/core/java/android/view/autofill/Dataset.aidl b/core/java/android/service/autofill/Dataset.aidl
similarity index 94%
rename from core/java/android/view/autofill/Dataset.aidl
rename to core/java/android/service/autofill/Dataset.aidl
index 2a8e67c..2342c5f 100644
--- a/core/java/android/view/autofill/Dataset.aidl
+++ b/core/java/android/service/autofill/Dataset.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.view.autofill;
+package android.service.autofill;
-parcelable Dataset;
\ No newline at end of file
+parcelable Dataset;
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
similarity index 60%
rename from core/java/android/view/autofill/Dataset.java
rename to core/java/android/service/autofill/Dataset.java
index 2708358..bd38c7f 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -14,59 +14,48 @@
* limitations under the License.
*/
-package android.view.autofill;
-
-import static android.view.autofill.Helper.DEBUG;
+package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.assist.AssistStructure.ViewNode;
import android.content.IntentSender;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
/**
- * A set of data that can be used to auto-fill an {@link Activity}.
+ * A set of data that can be used to auto-fill an {@link android.app.Activity}.
*
* <p>It contains:
*
* <ol>
* <li>A name used to identify the dataset in the UI.
* <li>A list of id/value pairs for the fields that can be auto-filled.
- * <li>An optional {@link Bundle} with extras (used only by the service creating it).
+ * <li>A list of savable ids in addition to the ones with a provided value.
* </ol>
*
- * @see FillResponse for examples.
+ * @see android.service.autofill.FillResponse for examples.
*/
public final class Dataset implements Parcelable {
- private final String mId;
+ private static final boolean DEBUG = false;
+
private final CharSequence mName;
private final ArrayList<AutoFillId> mFieldIds;
private final ArrayList<AutoFillValue> mFieldValues;
- private final Bundle mExtras;
private final IntentSender mAuthentication;
private Dataset(Builder builder) {
- mId = builder.mId;
mName = builder.mName;
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
- mExtras = builder.mExtras;
mAuthentication = builder.mAuthentication;
}
/** @hide */
- public @NonNull String getId() {
- return mId;
- }
-
- /** @hide */
public @NonNull CharSequence getName() {
return mName;
}
@@ -82,11 +71,6 @@
}
/** @hide */
- public @Nullable Bundle getExtras() {
- return mExtras;
- }
-
- /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -100,71 +84,32 @@
public String toString() {
if (!DEBUG) return super.toString();
- final StringBuilder builder = new StringBuilder("Dataset [id=").append(mId)
- .append(", name=").append(mName)
+ final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
.append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
- .append(", hasAuthentication=").append(mAuthentication != null)
- .append(", hasExtras=").append(mExtras != null);
+ .append(", hasAuthentication=").append(mAuthentication != null);
return builder.append(']').toString();
}
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final Dataset other = (Dataset) obj;
- if (mId == null) {
- if (other.mId != null) {
- return false;
- }
- } else if (!mId.equals(other.mId)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return mId != null ? mId.hashCode() : 0;
- }
-
/**
* A builder for {@link Dataset} objects. You must to provide at least
* one value for a field or set an authentication intent.
*/
public static final class Builder {
- private String mId;
private CharSequence mName;
private ArrayList<AutoFillId> mFieldIds;
private ArrayList<AutoFillValue> mFieldValues;
- private Bundle mExtras;
private IntentSender mAuthentication;
private boolean mDestroyed;
- /** @hide */
- // TODO(b/33197203): Remove once GCore migrates
- public Builder(@NonNull CharSequence name) {
- this(String.valueOf(System.currentTimeMillis()), name);
- }
-
/**
* Creates a new builder.
*
- * @param id A required id to identify this dataset for future interactions related to it.
* @param name Name used to identify the dataset in the UI. Typically it's the same value as
* the first field in the dataset (like username or email address) or a user-provided name
* (like "My Work Address").
*/
- public Builder(@NonNull String id, @NonNull CharSequence name) {
- mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty or null");
+ public Builder(@NonNull CharSequence name) {
mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null");
}
@@ -172,50 +117,36 @@
* Requires a dataset authentication before auto-filling the activity with this dataset.
*
* <p>This method is called when you need to provide an authentication
- * UI for the dataset. For example, when a dataset contains credit card information
+ * UI for the data set. For example, when a data set contains credit card information
* (such as number, expiration date, and verification code), you can display UI
* asking for the verification code to before filing in the data). Even if the
- * dataset is completely populated the system will launch the specified authentication
- * intent and will need your approval to fill it in. Since the dataset is "locked"
- * until the user authenticates it, typically this dataset name is masked
- * (for example, "VISA....1234"). Typically you would want to store the dataset
- * labels non-encypted and the actual sensitive data encrypted and not in memory.
+ * data set is completely populated the system will launch the specified authentication
+ * intent and will need your approval to fill it in. Since the data set is "locked"
+ * until the user authenticates it, typically this data set name is masked
+ * (for example, "VISA....1234"). Typically you would want to store the data set
+ * labels non-encrypted and the actual sensitive data encrypted and not in memory.
* This allows showing the labels in the UI while involving the user if one of
* the items with these labels is chosen. Note that if you use sensitive data as
- * a label, for example an email address, then it should also be encrypted.
- *</p>
+ * a label, for example an email address, then it should also be encrypted.</p>
*
- * <p>When a user selects this dataset, the system triggers the provided intent
- * whose extras will have the {@link android.content.Intent#EXTRA_AUTO_FILL_ITEM_ID id}
- * of the {@link android.view.autofill.Dataset dataset} to authenticate, the {@link
- * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} associated with this
- * dataset, and a {@link android.content.Intent#EXTRA_AUTO_FILL_CALLBACK callback}
- * to dispatch the authentication result.</p>
+ * <p>When a user triggers auto-fill, the system launches the provided intent
+ * whose extras will have the {@link
+ * android.view.autofill.AutoFillManager#EXTRA_ASSIST_STRUCTURE screen content}. Once
+ * you complete your authentication flow you should set the activity result to {@link
+ * android.app.Activity#RESULT_OK} and provide the fully populated {@link Dataset
+ * dataset} by setting it to the {@link
+ * android.view.autofill.AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. For example,
+ * if you provided an credit card information without the CVV for the data set in the
+ * {@link FillResponse response} then the returned data set should contain the
+ * CVV entry.</p>
*
- * <p>Once you complete your authentication flow you should use the provided callback
- * to notify for a failure or a success. In case of a success you need to provide
- * only the fully populated dataset that is being authenticated. For example, if you
- * provided a {@link FillResponse} with two {@link Dataset}s and marked that
- * only the first dataset needs an authentication then in the provided response
- * you need to provide only the fully populated dataset being authenticated instead
- * of both of them.
- * </p>
- *
- * <p>The indent sender mechanism allows you to have your authentication UI
- * implemented as an activity or a service or a receiver. However, the recommended
- * way is to do this is with an activity which the system will start in the
- * filled activity's task meaning it will properly work with back, recent apps, and
- * free-form multi-window, while avoiding the need for the "draw on top of other"
- * apps special permission. You can still theme your authentication activity's
- * UI to look like a dialog if desired.</p>
- *
- * <p></><strong>Note:</strong> Do not make the provided intent sender
+ * <p></><strong>Note:</strong> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
* platform needs to fill in the authentication arguments.</p>
*
- * @param authentication Intent to trigger your authentication flow.
+ * @param authentication Intent to an activity with your authentication flow.
*
- * @see android.app.PendingIntent#getIntentSender()
+ * @see android.app.PendingIntent
*/
public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
throwIfDestroyed();
@@ -226,7 +157,8 @@
/**
* Sets the value of a field.
*
- * @param id id returned by {@link ViewNode#getAutoFillId()}.
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutoFillId()}.
* @param value value to be auto filled.
*/
public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) {
@@ -249,18 +181,6 @@
}
/**
- * Sets a {@link Bundle} that will be passed to subsequent APIs that
- * manipulate this dataset. For example, they are passed in as {@link
- * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} to your
- * authentication flow.
- */
- public @NonNull Builder setExtras(@Nullable Bundle extras) {
- throwIfDestroyed();
- mExtras = extras;
- return this;
- }
-
- /**
* Creates a new {@link Dataset} instance. You should not interact
* with this builder once this method is called.
*/
@@ -292,21 +212,19 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mId);
parcel.writeCharSequence(mName);
parcel.writeTypedArrayList(mFieldIds, 0);
parcel.writeTypedArrayList(mFieldValues, 0);
- parcel.writeBundle(mExtras);
parcel.writeParcelable(mAuthentication, flags);
}
- public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {
+ public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
@Override
public Dataset createFromParcel(Parcel parcel) {
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = new Builder(parcel.readString(), parcel.readCharSequence());
+ final Builder builder = new Builder(parcel.readCharSequence());
final ArrayList<AutoFillId> ids = parcel.readTypedArrayList(null);
final ArrayList<AutoFillValue> values = parcel.readTypedArrayList(null);
final int idCount = (ids != null) ? ids.size() : 0;
@@ -316,7 +234,6 @@
AutoFillValue value = (valueCount > i) ? values.get(i) : null;
builder.setValue(id, value);
}
- builder.setExtras(parcel.readBundle());
builder.setAuthentication(parcel.readParcelable(null));
return builder.build();
}
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index a306809..69c9904 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -19,16 +19,13 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
-import android.view.autofill.FillResponse;
/**
* Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
*/
-public final class FillCallback implements Parcelable {
+public final class FillCallback {
private final IFillCallback mCallback;
private boolean mCalled;
@@ -37,11 +34,6 @@
mCallback = callback;
}
- /** @hide */
- private FillCallback(Parcel parcel) {
- mCallback = IFillCallback.Stub.asInterface(parcel.readStrongBinder());
- }
-
/**
* Notifies the Android System that an
* {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
@@ -79,33 +71,9 @@
}
}
- /** @hide */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** @hide */
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeStrongBinder(mCallback.asBinder());
- }
-
private void assertNotCalled() {
if (mCalled) {
throw new IllegalStateException("Already called");
}
}
-
- public static final Creator<FillCallback> CREATOR = new Creator<FillCallback>() {
- @Override
- public FillCallback createFromParcel(Parcel parcel) {
- return new FillCallback(parcel);
- }
-
- @Override
- public FillCallback[] newArray(int size) {
- return new FillCallback[size];
- }
- };
}
diff --git a/core/java/android/view/autofill/FillResponse.aidl b/core/java/android/service/autofill/FillResponse.aidl
similarity index 94%
rename from core/java/android/view/autofill/FillResponse.aidl
rename to core/java/android/service/autofill/FillResponse.aidl
index b018f15..e9c2d21 100644
--- a/core/java/android/view/autofill/FillResponse.aidl
+++ b/core/java/android/service/autofill/FillResponse.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.view.autofill;
+package android.service.autofill;
-parcelable FillResponse;
\ No newline at end of file
+parcelable FillResponse;
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
similarity index 73%
rename from core/java/android/view/autofill/FillResponse.java
rename to core/java/android/service/autofill/FillResponse.java
index 596a06c..ea36e64 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -13,30 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.view.autofill;
-
-import static android.view.autofill.Helper.DEBUG;
+package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
-
-import com.android.internal.util.Preconditions;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillManager;
/**
* Response for a {@link
- * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} and
+ * AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, FillCallback)} and
* authentication requests.
*
* <p>The response typically contains one or more {@link Dataset}s, each representing a set of
* fields that can be auto-filled together, and the Android system displays a dataset picker UI
- * affordance that the user must use before the {@link Activity} is filled with the dataset.
+ * affordance that the user must use before the {@link android.app.Activity} is filled with
+ * the dataset.
*
* <p>For example, for a login page with username/password where the user only has one account in
* the response could be:
@@ -65,9 +63,9 @@
* .build();
* </pre>
*
- * <p>If the user does not have any data associated with this {@link Activity} but the service wants
- * to offer the user the option to save the data that was entered, then the service could populate
- * the response with {@code savableIds} instead of {@link Dataset}s:
+ * <p>If the user does not have any data associated with this {@link android.app.Activity} but
+ * the service wants to offer the user the option to save the data that was entered, then the
+ * service could populate the response with {@code savableIds} instead of {@link Dataset}s:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -142,7 +140,7 @@
* #setAuthentication(IntentSender)} and {@link Dataset.Builder#setAuthentication(IntentSender)}.
* It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
* which would allow you to provide the dataset names to the user and if they choose one
- * them challenge the user to authenticate. For example, if the user has a home and a work
+ * them challenge the user to onAuthenticate. For example, if the user has a home and a work
* address the Home and Work labels could be stored unencrypted as they don't have any sensitive
* data while the address data is in an encrypted storage. If the user chooses Home, then the
* platform will start your authentication flow. If you encrypt all data and require auth
@@ -152,20 +150,16 @@
* Work options where they can pick one. Hence, you have flexibility how to implement your
* auth while storing labels non-encrypted and data encrypted provides a better user
* experience.</p>
- *
- * <p>Finally, the service can use {@link Dataset.Builder#setExtras(Bundle)} methods
- * to pass {@link Bundle extras} provided to all future calls related to a dataset,
- * for example during authentication and saving.</p>
*/
public final class FillResponse implements Parcelable {
- private final String mId;
+ private static final boolean DEBUG = false;
+
private final ArraySet<Dataset> mDatasets;
private final ArraySet<AutoFillId> mSavableIds;
private final Bundle mExtras;
private final IntentSender mAuthentication;
private FillResponse(@NonNull Builder builder) {
- mId = builder.mId;
mDatasets = builder.mDatasets;
mSavableIds = builder.mSavableIds;
mExtras = builder.mExtras;
@@ -173,11 +167,6 @@
}
/** @hide */
- public @NonNull String getId() {
- return mId;
- }
-
- /** @hide */
public @Nullable Bundle getExtras() {
return mExtras;
}
@@ -202,71 +191,49 @@
* one dataset or set an authentication intent.
*/
public static final class Builder {
- private final String mId;
private ArraySet<Dataset> mDatasets;
private ArraySet<AutoFillId> mSavableIds;
private Bundle mExtras;
private IntentSender mAuthentication;
private boolean mDestroyed;
- /** @hide */
- // TODO(b/33197203): Remove once GCore migrates
- public Builder() {
- this(String.valueOf(System.currentTimeMillis()));
- }
-
/**
* Creates a new {@link FillResponse} builder.
- *
- * @param id A required id to identify this dataset for future interactions related to it.
*/
- public Builder(@NonNull String id) {
- mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty or null");
+ public Builder() {
+
}
/**
* Requires a fill response authentication before auto-filling the activity with
- * any dataset in this response. This is typically useful when a user interaction
- * is required to unlock their data vault if you encrypt the dataset labels and
- * dataset data. It is recommended to encrypt only the sensitive data and not the
- * dataset labels which would allow auth on the dataset level leading to a better
- * user experience. Note that if you use sensitive data as a label, for example an
- * email address, then it should also be encrypted.
+ * any data set in this response.
*
- * <p>This method is called when you need to provide an authentication
- * UI for the fill response. For example, when the user's data is stored
- * encrypted and needs a user interaction to decrypt before offering fill
- * suggestions.</p>
+ * <p>This is typically useful when a user interaction is required to unlock their
+ * data vault if you encrypt the data set labels and data set data. It is recommended
+ * to encrypt only the sensitive data and not the data set labels which would allow
+ * auth on the data set level leading to a better user experience. Note that if you
+ * use sensitive data as a label, for example an email address, then it should also
+ * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
+ * activity which implements your authentication flow.</p>
*
- * <p>When a user initiates an auto fill, the system triggers the provided
- * intent whose extras will have the {@link android.content.Intent
- * #EXTRA_AUTO_FILL_ITEM_ID id} of the {@link android.view.autofill.FillResponse})
- * to authenticate, the {@link android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras}
- * associated with this response, and a {@link android.content.Intent
- * #EXTRA_AUTO_FILL_CALLBACK callback} to dispatch the authentication result.</p>
+ * <p>When a user triggers auto-fill, the system launches the provided intent
+ * whose extras will have the {@link
+ * AutoFillManager#EXTRA_ASSIST_STRUCTURE screen
+ * content}. Once you complete your authentication flow you should set the activity
+ * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated {@link
+ * FillResponse response} by setting it to the {@link
+ * AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra.
+ * For example, if you provided an empty {@link FillResponse resppnse} because the
+ * user's data was locked and marked that the response needs an authentication then
+ * in the response returned if authentication succeeds you need to provide all
+ * available data sets some of which may need to be further authenticated, for
+ * example a credit card whose CVV needs to be entered.</p>
*
- * <p>Once you complete your authentication flow you should use the provided callback
- * to notify for a failure or a success. In case of a success you need to provide
- * the fully populated response that is being authenticated. For example, if you
- * provided an empty {@link FillResponse} because the user's data was locked and
- * marked that the response needs an authentication then in the response returned
- * if authentication succeeds you need to provide all available datasets some of
- * which may need to be further authenticated, for example a credit card whose
- * CVV needs to be entered.</p>
- *
- * <p>The indent sender mechanism allows you to have your authentication UI
- * implemented as an activity or a service or a receiver. However, the recommended
- * way is to do this is with an activity which the system will start in the
- * filled activity's task meaning it will properly work with back, recent apps, and
- * free-form multi-window, while avoiding the need for the "draw on top of other"
- * apps special permission. You can still theme your authentication activity's
- * UI to look like a dialog if desired.</p>
- *
- * <p></><strong>Note:</strong> Do not make the provided intent sender
+ * <p></><strong>Note:</strong> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
* platform needs to fill in the authentication arguments.</p>
*
- * @param authentication Intent to trigger your authentication flow.
+ * @param authentication Intent to an activity with your authentication flow.
*
* @see android.app.PendingIntent#getIntentSender()
*/
@@ -313,8 +280,8 @@
/**
* Adds ids of additional fields that the service would be interested to save (through
- * {@link android.service.autofill.AutoFillService#onSaveRequest(
- * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)})
+ * {@link AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, SaveCallback)})
* but were not indirectly set through {@link #addDataset(Dataset)}.
*
* <p>See {@link FillResponse} for examples.
@@ -335,15 +302,13 @@
/**
* Sets a {@link Bundle} that will be passed to subsequent APIs that
- * manipulate this response. For example, they are passed in as {@link
- * android.content.Intent#EXTRA_AUTO_FILL_EXTRAS extras} to your
- * authentication flow and to subsequent calls to {@link
- * android.service.autofill.AutoFillService#onFillRequest(
+ * manipulate this response. For example, they are passed to subsequent
+ * calls to {@link AutoFillService#onFillRequest(
* android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
- * android.service.autofill.FillCallback)} and {@link
- * android.service.autofill.AutoFillService#onSaveRequest(
+ * FillCallback)} and {@link
+ * AutoFillService#onSaveRequest(
* android.app.assist.AssistStructure, Bundle,
- * android.service.autofill.SaveCallback)}.
+ * SaveCallback)}.
*/
public Builder setExtras(Bundle extras) {
throwIfDestroyed();
@@ -374,8 +339,7 @@
public String toString() {
if (!DEBUG) return super.toString();
final StringBuilder builder = new StringBuilder(
- "FillResponse: [id=").append(mId)
- .append(", datasets=").append(mDatasets)
+ "FillResponse: [datasets=").append(mDatasets)
.append(", savableIds=").append(mSavableIds)
.append(", hasExtras=").append(mExtras != null)
.append(", hasAuthentication=").append(mAuthentication != null);
@@ -393,7 +357,6 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mId);
parcel.writeTypedArraySet(mDatasets, 0);
parcel.writeTypedArraySet(mSavableIds, 0);
parcel.writeParcelable(mExtras, 0);
@@ -407,7 +370,7 @@
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = new Builder(parcel.readString());
+ final Builder builder = new Builder();
final ArraySet<Dataset> datasets = parcel.readTypedArraySet(null);
final int datasetCount = (datasets != null) ? datasets.size() : 0;
for (int i = 0; i < datasetCount; i++) {
diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/core/java/android/service/autofill/IAuthenticationCallback.aidl
similarity index 62%
copy from core/java/android/companion/ICompanionDeviceManagerService.aidl
copy to core/java/android/service/autofill/IAuthenticationCallback.aidl
index ff2a7eb..36b989d 100644
--- a/core/java/android/companion/ICompanionDeviceManagerService.aidl
+++ b/core/java/android/service/autofill/IAuthenticationCallback.aidl
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-package android.companion;
+package android.view.autofill;
-import android.companion.AssociationRequest;
-import android.companion.IOnAssociateCallback;
+import android.view.autofill.Dataset;
+import android.service.autofill.FillResponse;
-
-/** @hide */
-interface ICompanionDeviceManagerService {
- void startDiscovery(
- in AssociationRequest request,
- in IOnAssociateCallback callback,
- in String callingPackage);
+/**
+ * Callback for delivering authentication result.
+ *
+ * {@hide}
+ */
+interface IAutoFillAuthCallback {
+ void onSuccessForDataset(in Dataset dataset);
+ void onSuccessForFillResponse(in FillResponse response);
+ void onFailure(CharSequence message);
}
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
deleted file mode 100644
index 6cdb516..0000000
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.autofill;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.view.autofill.AutoFillId;
-import android.view.autofill.AutoFillValue;
-
-/**
- * Mediator between apps being auto-filled and auto-fill service implementations.
- *
- * {@hide}
- */
-oneway interface IAutoFillManagerService {
- // Methods called by AutoFillManager
- void startSession(in IBinder activityToken, in IBinder appCallback, in AutoFillId autoFillId,
- in Rect bounds, in AutoFillValue value);
- void updateSession(in IBinder activityToken, in AutoFillId id, in Rect bounds,
- in AutoFillValue value, int flags);
- void finishSession(in IBinder activityToken);
-}
diff --git a/core/java/android/service/autofill/IFillCallback.aidl b/core/java/android/service/autofill/IFillCallback.aidl
index 537403e..2bb3e9a 100644
--- a/core/java/android/service/autofill/IFillCallback.aidl
+++ b/core/java/android/service/autofill/IFillCallback.aidl
@@ -18,7 +18,7 @@
import android.os.ICancellationSignal;
-import android.view.autofill.FillResponse;
+import android.service.autofill.FillResponse;
/**
* Interface to receive the result of a save request.
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 46b3072..c6dd1df 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -23,8 +23,6 @@
/**
* Handles save requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
- *
- * <p>This class is thread safe.
*/
public final class SaveCallback {
private final ISaveCallback mCallback;
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index cb5f220..491eabc 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -97,6 +97,8 @@
}
mAudioTrack.setPlaybackPositionUpdateListener(this);
+ // Ensure we set the first marker if there is one.
+ updateMarker();
try {
byte[] buffer = null;
diff --git a/core/java/android/text/Emoji.java b/core/java/android/text/Emoji.java
index b6d720d..83810b0 100644
--- a/core/java/android/text/Emoji.java
+++ b/core/java/android/text/Emoji.java
@@ -164,6 +164,8 @@
public static int VARIATION_SELECTOR_16 = 0xFE0F;
+ public static int CANCEL_TAG = 0xE007F;
+
// Returns true if the given code point is regional indicator symbol.
public static boolean isRegionalIndicatorSymbol(int codepoint) {
return 0x1F1E6 <= codepoint && codepoint <= 0x1F1FF;
@@ -188,4 +190,13 @@
public static boolean isKeycapBase(int codePoint) {
return ('0' <= codePoint && codePoint <= '9') || codePoint == '#' || codePoint == '*';
}
+
+ /**
+ * Returns true if the character can be a part of tag_spec in emoji tag sequence.
+ *
+ * Note that 0xE007F (CANCEL TAG) is not included.
+ */
+ public static boolean isTagSpecChar(int codePoint) {
+ return 0xE0020 <= codePoint && codePoint <= 0xE007E;
+ }
}
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 3048a38..82e44dc 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -170,14 +170,24 @@
private final int mWeight;
private final boolean mIsItalic;
private ParcelFileDescriptor mFd;
+ private final int mResourceId;
- public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+ /**
+ * @hide
+ */
+ public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic,
+ int resourceId) {
mFontName = fontName;
mTtcIndex = ttcIndex;
mAxes = axes;
mWeight = weight;
mIsItalic = isItalic;
mFd = null;
+ mResourceId = resourceId;
+ }
+
+ public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+ this(fontName, ttcIndex, axes, weight, isItalic, 0);
}
public Font(Font origin) {
@@ -193,6 +203,7 @@
e.printStackTrace();
}
}
+ mResourceId = origin.mResourceId;
}
/**
@@ -254,6 +265,13 @@
/**
* @hide
*/
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ /**
+ * @hide
+ */
public Font(Parcel in) {
mFontName = in.readString();
mTtcIndex = in.readInt();
@@ -269,6 +287,7 @@
} else {
mFd = null;
}
+ mResourceId = in.readInt();
}
@Override
@@ -285,6 +304,7 @@
if (mFd != null) {
mFd.writeToParcel(out, flag);
}
+ out.writeInt(mResourceId);
}
@Override
@@ -382,22 +402,40 @@
private final List<Font> mFonts;
private final String mLanguage;
private final String mVariant;
+ private final String mProviderAuthority;
+ private final String mQuery;
public Family(String name, List<Font> fonts, String language, String variant) {
- this.mName = name;
- this.mFonts = fonts;
- this.mLanguage = language;
- this.mVariant = variant;
+ mName = name;
+ mFonts = fonts;
+ mLanguage = language;
+ mVariant = variant;
+ mProviderAuthority = null;
+ mQuery = null;
+ }
+
+ /**
+ * @hide
+ */
+ public Family(String providerAuthority, String query) {
+ mName = null;
+ mFonts = null;
+ mLanguage = null;
+ mVariant = null;
+ mProviderAuthority = providerAuthority;
+ mQuery = query;
}
public Family(Family origin) {
- this.mName = origin.mName;
- this.mLanguage = origin.mLanguage;
- this.mVariant = origin.mVariant;
- this.mFonts = new ArrayList<>();
+ mName = origin.mName;
+ mLanguage = origin.mLanguage;
+ mVariant = origin.mVariant;
+ mFonts = new ArrayList<>();
for (int i = 0; i < origin.mFonts.size(); i++) {
mFonts.add(new Font(origin.mFonts.get(i)));
}
+ mProviderAuthority = origin.mProviderAuthority;
+ mQuery = origin.mQuery;
}
/**
@@ -431,6 +469,20 @@
/**
* @hide
*/
+ public String getProviderAuthority() {
+ return mProviderAuthority;
+ }
+
+ /**
+ * @hide
+ */
+ public String getQuery() {
+ return mQuery;
+ }
+
+ /**
+ * @hide
+ */
public Family(Parcel in) {
mName = in.readString();
final int size = in.readInt();
@@ -440,6 +492,16 @@
}
mLanguage = in.readString();
mVariant = in.readString();
+ if (in.readInt() == 1) {
+ mProviderAuthority = in.readString();
+ } else {
+ mProviderAuthority = null;
+ }
+ if (in.readInt() == 1) {
+ mQuery = in.readString();
+ } else {
+ mQuery = null;
+ }
}
@Override
@@ -451,6 +513,14 @@
}
out.writeString(mLanguage);
out.writeString(mVariant);
+ out.writeInt(mProviderAuthority == null ? 0 : 1);
+ if (mProviderAuthority != null) {
+ out.writeString(mProviderAuthority);
+ }
+ out.writeInt(mQuery == null ? 0 : 1);
+ if (mQuery != null) {
+ out.writeString(mQuery);
+ }
}
@Override
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 90559dc..5f0a46d 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -145,8 +145,11 @@
// The number of following RIS code points is even.
final int STATE_EVEN_NUMBERED_RIS = 11;
+ // The offset is in emoji tag sequence.
+ final int STATE_IN_TAG_SEQUENCE = 12;
+
// The state machine has been stopped.
- final int STATE_FINISHED = 12;
+ final int STATE_FINISHED = 13;
int deleteCharCount = 0; // Char count to be deleted by backspace.
int lastSeenVSCharCount = 0; // Char count of previous variation selector.
@@ -173,6 +176,8 @@
state = STATE_BEFORE_KEYCAP;
} else if (Emoji.isEmoji(codePoint)) {
state = STATE_BEFORE_EMOJI;
+ } else if (codePoint == Emoji.CANCEL_TAG) {
+ state = STATE_IN_TAG_SEQUENCE;
} else {
state = STATE_FINISHED;
}
@@ -275,6 +280,20 @@
state = STATE_FINISHED;
}
break;
+ case STATE_IN_TAG_SEQUENCE:
+ if (Emoji.isTagSpecChar(codePoint)) {
+ deleteCharCount += 2; /* Char count of emoji tag spec character. */
+ // Keep the same state.
+ } else if (Emoji.isEmoji(codePoint)) {
+ deleteCharCount += Character.charCount(codePoint);
+ state = STATE_FINISHED;
+ } else {
+ // Couldn't find tag_base character. Delete the last tag_term character.
+ deleteCharCount = 2; // for U+E007F
+ state = STATE_FINISHED;
+ }
+ // TODO: Need handle emoji variation selectors. Issue 35224297
+ break;
default:
throw new IllegalArgumentException("state " + state + " is unknown");
}
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 24c119f..31ed549 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -29,9 +29,6 @@
/**
* A movement method that traverses links in the text buffer and scrolls if necessary.
* Supports clicking on links with DPad Center or Enter.
- *
- * <p>Note: Starting from Android 8.0 (API level 25) this class no longer handles the touch
- * clicks.
*/
public class LinkMovementMethod extends ScrollingMovementMethod {
private static final int CLICK = 1;
@@ -198,7 +195,7 @@
MotionEvent event) {
int action = event.getAction();
- if (action == MotionEvent.ACTION_DOWN) {
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
@@ -215,9 +212,13 @@
ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
if (links.length != 0) {
- Selection.setSelection(buffer,
+ if (action == MotionEvent.ACTION_UP) {
+ links[0].onClick(widget);
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ Selection.setSelection(buffer,
buffer.getSpanStart(links[0]),
buffer.getSpanEnd(links[0]));
+ }
return true;
} else {
Selection.removeSelection(buffer);
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 41c44f1..7af1020 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -132,7 +132,7 @@
}
public void setSurfaceTexture(SurfaceTexture surface) {
- nSetSurfaceTexture(mFinalizer.get(), surface, false);
+ nSetSurfaceTexture(mFinalizer.get(), surface);
mRenderer.pushLayerUpdate(this);
}
@@ -148,7 +148,6 @@
private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
private static native void nSetLayerPaint(long layerUpdater, long paint);
private static native void nSetTransform(long layerUpdater, long matrix);
- private static native void nSetSurfaceTexture(long layerUpdater,
- SurfaceTexture surface, boolean isAlreadyAttached);
+ private static native void nSetSurfaceTexture(long layerUpdater, SurfaceTexture surface);
private static native void nUpdateSurfaceTexture(long layerUpdater);
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 2129039..5f55bdc 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1008,7 +1008,6 @@
* </p>
*
* @see #getAxisValue(int, int)
- * {@hide}
*/
public static final int AXIS_SCROLL = 26;
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index cc19539..9ce23e6 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -25,7 +25,8 @@
/**
* Container for storing additional per-view data generated by {@link View#onProvideStructure
- * View.onProvideStructure}.
+ * View.onProvideStructure} and {@link View#onProvideAutoFillStructure
+ * View.onProvideAutoFillStructure}.
*/
public abstract class ViewStructure {
@@ -33,7 +34,9 @@
* Flag used when adding virtual views for auto-fill, it indicates the contents of the view
* (such as * {@link android.app.assist.AssistStructure.ViewNode#getText()} and
* {@link android.app.assist.AssistStructure.ViewNode#getAutoFillValue()})
- * can be passed to the {@link android.service.autofill.AutoFillService}.
+ * can be passed to the {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} call.
*/
public static final int AUTO_FILL_FLAG_SANITIZED = 0x1;
@@ -275,7 +278,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0}.
+ * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure newChild(int index, int virtualId, int flags);
@@ -296,7 +299,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0}.
+ * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
@@ -335,4 +338,17 @@
/** @hide */
public abstract AutoFillId getAutoFillId();
+
+ /**
+ * Sets the URL represented by this node.
+ *
+ * <p>Typically used in the following situations:
+ *
+ * <ol>
+ * <li>In a {@link android.app.assist.AssistStructure.WindowNode#getRootViewNode()}, to set up
+ * the main URL of an HTML page.
+ * <li>On child nodes represening hyperlinks.
+ * </ol>
+ */
+ public abstract void setUrl(String url);
}
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 6e2a92c..a541a4c 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -282,6 +282,28 @@
public abstract void clearLastInputMethodWindowForTransition();
/**
+ * Notifies WindowManagerService that the current IME window status is being changed.
+ *
+ * <p>Only {@link com.android.server.InputMethodManagerService} is the expected and tested
+ * caller of this method.</p>
+ *
+ * @param imeToken token to track the active input method. Corresponding IME windows can be
+ * identified by checking {@link android.view.WindowManager.LayoutParams#token}.
+ * Note that there is no guarantee that the corresponding window is already
+ * created
+ * @param imeWindowVisible whether the active IME thinks that its window should be visible or
+ * hidden, no matter how WindowManagerService will react / has reacted
+ * to corresponding API calls. Note that this state is not guaranteed
+ * to be synchronized with state in WindowManagerService.
+ * @param targetWindowToken token to identify the target window that the IME is associated with.
+ * {@code null} when application, system, or the IME itself decided to
+ * change its window visibility before being associated with any target
+ * window.
+ */
+ public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+ boolean imeWindowVisible, @Nullable IBinder targetWindowToken);
+
+ /**
* Returns true when the hardware keyboard is available.
*/
public abstract boolean isHardKeyboardAvailable();
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index 58607ba..f7a1b61 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -16,40 +16,101 @@
package android.view.autofill;
-import static android.view.autofill.Helper.VERBOSE;
-
-import android.annotation.Nullable;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.RemoteException;
-import android.service.autofill.IAutoFillManagerService;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
+import java.lang.ref.WeakReference;
+import java.util.List;
+
/**
* App entry point to the AutoFill Framework.
*/
// TODO(b/33197203): improve this javadoc
//TODO(b/33197203): restrict manager calls to activity
public final class AutoFillManager {
+ private static final boolean DEBUG = false;
private static final String TAG = "AutoFillManager";
+ /**
+ * Intent extra: The assist structure which captures the filled screen.
+ * <p>
+ * Type: {@link android.app.assist.AssistStructure}
+ * </p>
+ */
+ public static final String EXTRA_ASSIST_STRUCTURE =
+ "android.view.autofill.extra.ASSIST_STRUCTURE";
+
+ /**
+ * Intent extra: The result of an authentication operation. It is
+ * either a fully populated {@link android.service.autofill.FillResponse}
+ * or a fully populated {@link android.service.autofill.Dataset} if
+ * a response or a dataset is being authenticated respectively.
+ *
+ * <p>
+ * Type: {@link android.service.autofill.FillResponse} or a
+ * {@link android.service.autofill.Dataset}
+ * </p>
+ */
+ public static final String EXTRA_AUTHENTICATION_RESULT =
+ "android.view.autofill.extra.AUTHENTICATION_RESULT";
+
/** @hide */ public static final int FLAG_START_SESSION = 0x1;
/** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2;
/** @hide */ public static final int FLAG_FOCUS_LOST = 0x4;
/** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8;
- private final IAutoFillManagerService mService;
- private final Context mContext;
+ // These are activities that may have auto-fill UI which are keyed off their tokens.
+ // This is done instead of the activity setting the client in the auto-fill manager
+ // to avoid unnecessary instantiation of the manager and do this only if there is an
+ // auto-fillable focused. This has only the cost of loading the class vs creating an
+ // auto-fill manager for every activity even one that cannot be filled.
+ private static final ArrayMap<IBinder, AutoFillClient> sPendingClients = new ArrayMap<>();
- private AutoFillSession mSession;
+ private final Rect mTempRect = new Rect();
+
+ private final IAutoFillManager mService;
+ private IAutoFillManagerClient mServiceClient;
+
+ private Context mContext;
+
+ private AutoFillClient mClient;
+
+ private boolean mHasSession;
+ private boolean mEnabled;
+
+ /** @hide */
+ public interface AutoFillClient {
+ /**
+ * Asks the client to perform an auto-fill.
+ *
+ * @param ids The values to auto-fill
+ * @param values The values to auto-fill
+ */
+ void autoFill(List<AutoFillId> ids, List<AutoFillValue> values);
+
+ /**
+ * Asks the client to start an authentication flow.
+ *
+ * @param intent The authentication intent.
+ * @param fillInIntent The authentication fill-in intent.
+ */
+ void authenticate(IntentSender intent, Intent fillInIntent);
+ }
/**
* @hide
*/
- public AutoFillManager(Context context, IAutoFillManagerService service) {
+ public AutoFillManager(Context context, IAutoFillManager service) {
mContext = context;
mService = service;
}
@@ -61,27 +122,26 @@
* @param gainFocus whether focus was gained or lost.
*/
public void focusChanged(View view, boolean gainFocus) {
- if (mSession == null) {
- // Starts new session.
- final Rect bounds = new Rect();
- view.getBoundsOnScreen(bounds);
- final AutoFillId id = getAutoFillId(view);
- final AutoFillValue value = view.getAutoFillValue();
- startSession(id, bounds, value);
+ ensureServiceClientAddedIfNeeded();
+
+ if (!mEnabled) {
return;
}
- if (!mSession.isEnabled()) {
- // Auto-fill is disabled for this session.
- return;
- }
-
- // Update focus on existing session.
- final Rect bounds = new Rect();
+ final Rect bounds = mTempRect;
view.getBoundsOnScreen(bounds);
final AutoFillId id = getAutoFillId(view);
final AutoFillValue value = view.getAutoFillValue();
- updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+
+ if (!mHasSession) {
+ if (gainFocus) {
+ // Starts new session.
+ startSession(id, bounds, value);
+ }
+ } else {
+ // Update focus on existing session.
+ updateSession(id, bounds, value, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+ }
}
/**
@@ -93,21 +153,23 @@
* @param gainFocus whether focus was gained or lost.
*/
public void virtualFocusChanged(View parent, int childId, Rect bounds, boolean gainFocus) {
- if (mSession == null) {
- // Starts new session.
- final AutoFillId id = getAutoFillId(parent, childId);
- startSession(id, bounds, null);
+ ensureServiceClientAddedIfNeeded();
+
+ if (!mEnabled) {
return;
}
- if (!mSession.isEnabled()) {
- // Auto-fill is disabled for this session.
- return;
- }
-
- // Update focus on existing session.
final AutoFillId id = getAutoFillId(parent, childId);
- updateSession(id, bounds, null, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+
+ if (!mHasSession) {
+ if (gainFocus) {
+ // Starts new session.
+ startSession(id, bounds, null);
+ }
+ } else {
+ // Update focus on existing session.
+ updateSession(id, bounds, null, gainFocus ? FLAG_FOCUS_GAINED : FLAG_FOCUS_LOST);
+ }
}
/**
@@ -116,7 +178,11 @@
* @param view view whose focus changed.
*/
public void valueChanged(View view) {
- if (mSession == null) return;
+ ensureServiceClientAddedIfNeeded();
+
+ if (!mEnabled || !mHasSession) {
+ return;
+ }
final AutoFillId id = getAutoFillId(view);
final AutoFillValue value = view.getAutoFillValue();
@@ -132,7 +198,11 @@
* @param value new value of the child.
*/
public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
- if (mSession == null) return;
+ ensureServiceClientAddedIfNeeded();
+
+ if (!mEnabled || !mHasSession) {
+ return;
+ }
final AutoFillId id = getAutoFillId(parent, childId);
updateSession(id, null, value, FLAG_VALUE_CHANGED);
@@ -145,30 +215,51 @@
* call this method after the form is submitted and another page is rendered.
*/
public void reset() {
- if (mSession == null) return;
+ ensureServiceClientAddedIfNeeded();
- final IBinder activityToken = mSession.mToken.get();
- if (activityToken == null) {
- Log.wtf(TAG, "finishSession(): token already GC'ed");
+ if (!mEnabled && !mHasSession) {
return;
}
- try {
- mService.finishSession(activityToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } finally {
- mSession = null;
- }
+
+ finishSession();
}
- /**
- * Gets the current session, if any.
- *
- * @hide
- */
- @Nullable
- public AutoFillSession getSession() {
- return mSession;
+ /** @hide */
+ public static void addClient(IBinder token, AutoFillClient client) {
+ sPendingClients.put(token, client);
+ }
+
+ /** @hide */
+ public static boolean isClientActive(IBinder token) {
+ return !sPendingClients.containsKey(token);
+ }
+
+ private void activateClient() {
+ mClient = sPendingClients.remove(mContext.getActivityToken());
+ }
+
+ private AutoFillClient getClient() {
+ if (mClient == null) {
+ return sPendingClients.get(mContext.getActivityToken());
+ }
+ return mClient;
+ }
+
+ /** @hide */
+ public void onAuthenticationResult(Intent data) {
+ if (data == null) {
+ return;
+ }
+ Parcelable result = data.getParcelableExtra(
+ EXTRA_AUTHENTICATION_RESULT);
+ Bundle responseData = new Bundle();
+ responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+ try {
+ mService.setAuthenticationResult(responseData,
+ mContext.getActivityToken(), mContext.getUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering authentication result", e);
+ }
}
private AutoFillId getAutoFillId(View view) {
@@ -180,34 +271,98 @@
}
private void startSession(AutoFillId id, Rect bounds, AutoFillValue value) {
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value);
}
-
- final IBinder activityToken = mContext.getActivityToken();
- mSession = new AutoFillSession(this, activityToken);
- final IBinder appCallback = mSession.getCallback().asBinder();
try {
- mService.startSession(activityToken, appCallback, id, bounds, value);
+ mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(),
+ id, bounds, value, mContext.getUserId());
+ mHasSession = true;
+ activateClient();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void finishSession() {
+ if (DEBUG) {
+ Log.v(TAG, "finishSession()");
+ }
+ mHasSession = false;
+ try {
+ mService.finishSession(mContext.getActivityToken(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private void updateSession(AutoFillId id, Rect bounds, AutoFillValue value, int flags) {
- if (VERBOSE) {
+ if (DEBUG) {
Log.v(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ ", flags=" + flags);
}
-
- final IBinder activityToken = mSession.mToken.get();
- if (activityToken == null) {
- return;
- }
try {
- mService.updateSession(activityToken, id, bounds, value, flags);
+ mService.updateSession(mContext.getActivityToken(), id, bounds, value, flags,
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
+ private void ensureServiceClientAddedIfNeeded() {
+ if (getClient() == null) {
+ return;
+ }
+ if (mServiceClient == null) {
+ mServiceClient = new AutoFillManagerClient(this);
+ try {
+ mEnabled = mService.addClient(mServiceClient, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private static final class AutoFillManagerClient extends IAutoFillManagerClient.Stub {
+ private final WeakReference<AutoFillManager> mAutoFillManager;
+
+ AutoFillManagerClient(AutoFillManager autoFillManager) {
+ mAutoFillManager = new WeakReference<>(autoFillManager);
+ }
+
+ @Override
+ public void setState(boolean enabled) {
+ final AutoFillManager autoFillManager = mAutoFillManager.get();
+ if (autoFillManager != null) {
+ autoFillManager.mContext.getMainThreadHandler().post(() ->
+ autoFillManager.mEnabled = enabled);
+ }
+ }
+
+ @Override
+ public void autoFill(List<AutoFillId> ids, List<AutoFillValue> values) {
+ // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+ // dataset.extras to service
+ final AutoFillManager autoFillManager = mAutoFillManager.get();
+ if (autoFillManager != null) {
+ autoFillManager.mContext.getMainThreadHandler().post(() -> {
+ if (autoFillManager.getClient() != null) {
+ autoFillManager.getClient().autoFill(ids, values);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void authenticate(IntentSender intent, Intent fillInIntent) {
+ final AutoFillManager autoFillManager = mAutoFillManager.get();
+ if (autoFillManager != null) {
+ autoFillManager.mContext.getMainThreadHandler().post(() -> {
+ if (autoFillManager.getClient() != null) {
+ autoFillManager.getClient().authenticate(intent, fillInIntent);
+ }
+ });
+ }
+ }
+ }
}
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
deleted file mode 100644
index 64df62f..0000000
--- a/core/java/android/view/autofill/AutoFillSession.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.view.autofill;
-
-import static android.view.autofill.Helper.DEBUG;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.IBinder;
-import android.service.autofill.IAutoFillAppCallback;
-import android.util.Log;
-import android.view.View;
-
-import java.lang.ref.WeakReference;
-
-/**
- * An auto-fill session associated with an activity.
- *
- * @hide
- */
-public final class AutoFillSession {
-
- private static final String TAG = "AutoFillSession";
-
- private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
-
- @Override
- public void enableSession() {
- if (DEBUG) Log.d(TAG, "enableSession()");
-
- mEnabled = true;
- }
-
- @Override
- public void autoFill(Dataset dataset) {
- final Activity activity = mActivity.get();
- if (activity == null) {
- if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed");
- return;
- }
- // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
- // dataset.extras to service
- activity.runOnUiThread(() -> {
- final View root = activity.getWindow().getDecorView().getRootView();
- final int itemCount = dataset.getFieldIds().size();
- for (int i = 0; i < itemCount; i++) {
- final AutoFillId id = dataset.getFieldIds().get(i);
- final AutoFillValue value = dataset.getFieldValues().get(i);
- final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.w(TAG, "autoFill(): no View with id " + viewId);
- continue;
- }
-
- if (id.isVirtual()) {
- view.autoFillVirtual(id.getVirtualChildId(), value);
- } else {
- view.autoFill(value);
- }
- }
- });
- }
-
- @Override
- public void startIntentSender(IntentSender intent, Intent fillInIntent) {
- final Activity activity = mActivity.get();
- if (activity != null) {
- activity.runOnUiThread(() -> {
- try {
- activity.startIntentSender(intent, fillInIntent, 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- Log.e(TAG, "startIntentSender() failed for intent:" + intent, e);
- }
- });
- }
- }
- };
-
- private final AutoFillManager mAfm;
- private WeakReference<Activity> mActivity;
-
- // Reference to the token, which is used by the server.
- final WeakReference<IBinder> mToken;
-
- private boolean mEnabled;
-
- public AutoFillSession(AutoFillManager afm, IBinder token) {
- mToken = new WeakReference<>(token);
- mAfm = afm;
- }
-
- /**
- * Called by the {@link Activity} when it was asked to provider auto-fill data.
- */
- public void attachActivity(Activity activity) {
- if (mActivity != null) {
- Log.w(TAG, "attachActivity(): already attached");
- return;
- }
- mActivity = new WeakReference<>(activity);
- }
-
- /**
- * Checks whether auto-fill is enabled for this session, as decided by the
- * {@code AutoFillManagerService}.
- */
- public boolean isEnabled() {
- return mEnabled;
- }
-
- /**
- * Notifies the manager that a session finished.
- */
- // TODO(b/33197203): hook it to other lifecycle events like fragments transition
- public void finishSession() {
- if (mAfm != null) {
- try {
- mAfm.reset();
- } catch (RuntimeException e) {
- Log.w(TAG, "Failed to finish session for " + mToken.get() + ": " + e);
- }
- }
- }
-
- public IAutoFillAppCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public String toString() {
- if (!DEBUG) return super.toString();
-
- return "AutoFillSession[activityoken=" + mToken.get() + "]";
- }
-}
diff --git a/core/java/android/view/autofill/FieldId.aidl b/core/java/android/view/autofill/FieldId.aidl
deleted file mode 100644
index 35af645..0000000
--- a/core/java/android/view/autofill/FieldId.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-parcelable FieldId;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
new file mode 100644
index 0000000..0433a8f
--- /dev/null
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
+import android.view.autofill.IAutoFillManagerClient;
+
+/**
+ * Mediator between apps being auto-filled and auto-fill service implementations.
+ *
+ * {@hide}
+ */
+interface IAutoFillManager {
+ boolean addClient(in IAutoFillManagerClient client, int userId);
+ oneway void startSession(in IBinder activityToken, in IBinder appCallback,
+ in AutoFillId autoFillId, in Rect bounds, in AutoFillValue value, int userId);
+ oneway void updateSession(in IBinder activityToken, in AutoFillId id, in Rect bounds,
+ in AutoFillValue value, int flags, int userId);
+ oneway void finishSession(in IBinder activityToken, int userId);
+ oneway void setAuthenticationResult(in Bundle data,
+ in IBinder activityToken, int userId);
+}
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
similarity index 64%
rename from core/java/android/service/autofill/IAutoFillAppCallback.aidl
rename to core/java/android/view/autofill/IAutoFillManagerClient.aidl
index c2e72e8..45f363d 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -14,34 +14,33 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.view.autofill;
import java.util.List;
import android.content.Intent;
import android.content.IntentSender;
-import android.view.autofill.Dataset;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
/**
* Object running in the application process and responsible for auto-filling it.
*
* @hide
*/
-// TODO(b/33197203): rename IAutoFillAppSession
-oneway interface IAutoFillAppCallback {
+oneway interface IAutoFillManagerClient {
+ /**
+ * Notifies the client when the auto-fill enabled state changed.
+ */
+ void setState(boolean enabled);
+
/**
* Auto-fills the activity with the contents of a dataset.
*/
- void autoFill(in Dataset dataset);
+ void autoFill(in List<AutoFillId> ids, in List<AutoFillValue> values);
/**
- * Start an intent sender from the context of the filled app
+ * Authenticates a fill response or a data set.
*/
- void startIntentSender(in IntentSender intent, in Intent fillInIntent);
-
- /**
- * Called by system_service to enable auto-fill in a session, after it was asynchronously
- * started by the manager.
- */
- void enableSession();
+ void authenticate(in IntentSender intent, in Intent fillInIntent);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17cd446..5572cbb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -121,7 +121,6 @@
import android.view.Choreographer;
import android.view.ContextMenu;
import android.view.DragEvent;
-import android.view.GestureDetector;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyCharacterMap;
@@ -682,8 +681,6 @@
*/
private Editor mEditor;
- private GestureDetector mClickableSpanOnClickGestureDetector;
-
private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
private static final int DEVICE_PROVISIONED_NO = 1;
private static final int DEVICE_PROVISIONED_YES = 2;
@@ -9319,24 +9316,21 @@
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- // Lazily create the clickable span gesture detector only if it looks like it
- // might be useful.
- if (action == MotionEvent.ACTION_DOWN && mClickableSpanOnClickGestureDetector == null
- && shouldUseClickableSpanOnClickGestureDetector()) {
- ClickableSpan[] links = ((Spannable) mText).getSpans(
- getSelectionStart(), getSelectionEnd(),
- ClickableSpan.class);
+ final boolean textIsSelectable = isTextSelectable();
+ if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
+ // The LinkMovementMethod which should handle taps on links has not been installed
+ // on non editable text that support text selection.
+ // We reproduce its behavior here to open links for these.
+ ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
+ getSelectionEnd(), ClickableSpan.class);
+
if (links.length > 0) {
- mClickableSpanOnClickGestureDetector =
- createClickableSpanOnClickGestureDetector();
+ links[0].onClick(this);
+ handled = true;
}
}
- if (mClickableSpanOnClickGestureDetector != null) {
- handled |= mClickableSpanOnClickGestureDetector.onTouchEvent(event);
- }
-
- if (touchIsFinished && (isTextEditable() || isTextSelectable())) {
+ if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
@@ -9754,31 +9748,6 @@
mEditor.onLocaleChanged();
}
- private GestureDetector createClickableSpanOnClickGestureDetector() {
- return new GestureDetector(mContext,
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (shouldUseClickableSpanOnClickGestureDetector()) {
- ClickableSpan[] links = ((Spannable) mText).getSpans(
- getSelectionStart(), getSelectionEnd(),
- ClickableSpan.class);
- if (links.length > 0) {
- links[0].onClick(TextView.this);
- return true;
- }
- }
- return false;
- }
- });
- }
-
- private boolean shouldUseClickableSpanOnClickGestureDetector() {
- return mLinksClickable && (mMovement != null) &&
- (mMovement instanceof LinkMovementMethod
- || (mAutoLinkMask != 0 && isTextSelectable()));
- }
-
/**
* This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
* Made available to achieve a consistent behavior.
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index d4baa18..84c8f7a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -340,8 +340,7 @@
}
@Override
- public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
- boolean alwaysUseOption) {
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
@@ -367,6 +366,9 @@
@Override
public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ // Note that this is only safe because the Intent handled by the ChooserActivity is
+ // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
+ // method can not be replaced in the ResolverActivity whole hog.
return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
super.shouldAutoLaunchSingleChoice(target));
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 4014217..3c1a180 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -20,22 +20,11 @@
import android.annotation.StringRes;
import android.annotation.UiThread;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.provider.MediaStore;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.widget.AbsListView;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
-
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -50,16 +39,23 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
+import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
@@ -67,7 +63,9 @@
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.widget.ResolverDrawerLayout;
@@ -92,7 +90,6 @@
protected ResolveListAdapter mAdapter;
private boolean mSafeForwardingMode;
- private boolean mAlwaysUseOption;
private AbsListView mAdapterView;
private Button mAlwaysButton;
private Button mOnceButton;
@@ -108,13 +105,15 @@
private CharSequence mTitle;
private int mDefaultTitleResId;
+ // Whether or not this activity supports choosing a default handler for the intent.
+ private boolean mSupportsAlwaysUseOption;
protected ResolverDrawerLayout mResolverDrawerLayout;
protected PackageManager mPm;
protected int mLaunchedFromUid;
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
- private Runnable mPostListBuildRunnable;
+ private Runnable mPostListReadyRunnable;
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -230,13 +229,14 @@
*/
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, Intent[] initialIntents,
- List<ResolveInfo> rList, boolean alwaysUseOption) {
- onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption);
+ List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
+ onCreate(savedInstanceState, intent, title, 0, initialIntents, rList,
+ supportsAlwaysUseOption);
}
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, int defaultTitleRes, Intent[] initialIntents,
- List<ResolveInfo> rList, boolean alwaysUseOption) {
+ List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
setTheme(R.style.Theme_DeviceDefault_Resolver);
super.onCreate(savedInstanceState);
@@ -262,7 +262,7 @@
mPackageMonitor.register(this, getMainLooper(), false);
mRegistered = true;
mReferrerPackage = getReferrerPackageName();
- mAlwaysUseOption = alwaysUseOption;
+ mSupportsAlwaysUseOption = supportsAlwaysUseOption;
final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
mIconDpi = am.getLauncherLargeIconDensity();
@@ -378,8 +378,11 @@
final DisplayResolveInfo dri = mAdapter.getOtherProfile();
if (dri != null) {
mProfileView.setVisibility(View.VISIBLE);
- final TextView text = (TextView) mProfileView.findViewById(R.id.profile_button);
- text.setText(dri.getDisplayLabel());
+ View text = mProfileView.findViewById(R.id.profile_button);
+ if (!(text instanceof TextView)) {
+ text = mProfileView.findViewById(R.id.text1);
+ }
+ ((TextView) text).setText(dri.getDisplayLabel());
} else {
mProfileView.setVisibility(View.GONE);
}
@@ -421,7 +424,7 @@
final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action);
// While there may already be a filtered item, we can only use it in the title if the list
// is already sorted and all information relevant to it is already in the list.
- final boolean named = mAdapter.getFilteredPosition() > 0;
+ final boolean named = mAdapter.getFilteredPosition() >= 0;
if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
return getString(defaultTitleRes);
} else {
@@ -512,15 +515,16 @@
if (!isChangingConfigurations() && mPickOptionRequest != null) {
mPickOptionRequest.cancel();
}
- if (mPostListBuildRunnable != null) {
- getMainThreadHandler().removeCallbacks(mPostListBuildRunnable);
+ if (mPostListReadyRunnable != null) {
+ getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
+ mPostListReadyRunnable = null;
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
- if (mAlwaysUseOption) {
+ if (mSupportsAlwaysUseOption) {
final int checkedPos = mAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
mLastSelected = checkedPos;
@@ -575,17 +579,18 @@
public void onButtonClick(View v) {
final int id = v.getId();
- startSelected(mAlwaysUseOption ?
- mAdapterView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
+ startSelected(mAdapter.hasFilteredItem() ?
+ mAdapter.getFilteredPosition():
+ mAdapterView.getCheckedItemPosition(),
id == R.id.button_always,
- mAlwaysUseOption);
+ !mAdapter.hasFilteredItem());
}
- public void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
if (isFinishing()) {
return;
}
- ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
+ ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
Toast.makeText(this, String.format(getResources().getString(
com.android.internal.R.string.activity_resolver_work_profiles_support),
@@ -594,15 +599,15 @@
return;
}
- TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
+ TargetInfo target = mAdapter.targetInfoForPosition(which, hasIndexBeenFiltered);
if (target == null) {
return;
}
if (onTargetSelected(target, always)) {
- if (always && filtered) {
+ if (always && mSupportsAlwaysUseOption) {
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
- } else if (filtered) {
+ } else if (mSupportsAlwaysUseOption) {
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
} else {
@@ -627,7 +632,7 @@
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
- if (intent != null && (mAlwaysUseOption || mAdapter.hasFilteredItem())
+ if (intent != null && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
&& mAdapter.mUnfilteredResolveList != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
@@ -714,7 +719,18 @@
if (filter != null) {
final int N = mAdapter.mUnfilteredResolveList.size();
- ComponentName[] set = new ComponentName[N];
+ ComponentName[] set;
+ // If we don't add back in the component for forwarding the intent to a managed
+ // profile, the preferred activity may not be updated correctly (as the set of
+ // components we tell it we knew about will have changed).
+ final boolean needToAddBackProfileForwardingComponent
+ = mAdapter.mOtherProfile != null;
+ if (!needToAddBackProfileForwardingComponent) {
+ set = new ComponentName[N];
+ } else {
+ set = new ComponentName[N + 1];
+ }
+
int bestMatch = 0;
for (int i=0; i<N; i++) {
ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
@@ -722,6 +738,13 @@
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
+
+ if (needToAddBackProfileForwardingComponent) {
+ set[N] = mAdapter.mOtherProfile.getResolvedComponentName();
+ final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
+ if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
+ }
+
if (alwaysCheck) {
final int userId = getUserId();
final PackageManager pm = getPackageManager();
@@ -859,15 +882,15 @@
// a more complicated UI that the current voice interaction flow is not able
// to handle.
mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
- mLaunchedFromUid, mAlwaysUseOption && !isVoiceInteraction());
+ mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction());
boolean rebuildCompleted = mAdapter.rebuildList();
- if (mAdapter.hasFilteredItem()) {
+ if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
- mAlwaysUseOption = false;
} else {
mLayoutId = getLayoutResource();
}
+ setContentView(mLayoutId);
int count = mAdapter.getUnfilteredCount();
@@ -887,22 +910,21 @@
}
}
- setContentView(mLayoutId);
+
mAdapterView = (AbsListView) findViewById(R.id.resolver_list);
if (count == 0 && mAdapter.mPlaceholderCount == 0) {
- final TextView empty = (TextView) findViewById(R.id.empty);
- empty.setVisibility(View.VISIBLE);
+ final TextView emptyView = (TextView) findViewById(R.id.empty);
+ emptyView.setVisibility(View.VISIBLE);
mAdapterView.setVisibility(View.GONE);
} else {
mAdapterView.setVisibility(View.VISIBLE);
- onPrepareAdapterView(mAdapterView, mAdapter, mAlwaysUseOption);
+ onPrepareAdapterView(mAdapterView, mAdapter);
}
return false;
}
- public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
- boolean alwaysUseOption) {
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
final boolean useHeader = adapter.hasFilteredItem();
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -912,7 +934,7 @@
adapterView.setOnItemClickListener(listener);
adapterView.setOnItemLongClickListener(listener);
- if (alwaysUseOption) {
+ if (mSupportsAlwaysUseOption) {
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
@@ -969,16 +991,19 @@
}
public void resetAlwaysOrOnceButtonBar() {
- if (mAlwaysUseOption || mAdapter.mLastChosen != null) {
+ if (mSupportsAlwaysUseOption) {
final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+ } else {
+ Log.e(TAG, "Layout unexpectedly does not have a button bar");
}
}
- if (mAdapter.getFilteredPosition() >= 0) {
+ if (useLayoutWithDefault()
+ && mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
mOnceButton.setEnabled(true);
return;
@@ -992,6 +1017,10 @@
}
}
+ private boolean useLayoutWithDefault() {
+ return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
+ }
+
/**
* Check a simple match for the component of two ResolveInfos.
*/
@@ -1293,6 +1322,7 @@
mPlaceholderCount = count;
}
+ @Nullable
public DisplayResolveInfo getFilteredItem() {
if (mFilterLastUsed && mLastChosenPosition >= 0) {
// Not using getItem since it offsets to dodge this position for the list
@@ -1336,14 +1366,10 @@
*/
protected boolean rebuildList() {
List<ResolvedComponentInfo> currentResolveList = null;
- try {
- mLastChosen = mResolverListController.getLastChosen();
- } catch (RemoteException re) {
- Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
- }
-
// Clear the value of mOtherProfile from previous call.
mOtherProfile = null;
+ mLastChosen = null;
+ mLastChosenPosition = -1;
mDisplayList.clear();
if (mBaseResolveList != null) {
currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -1366,6 +1392,30 @@
mUnfilteredResolveList = originalList;
}
}
+
+ // So far we only support a single other profile at a time.
+ // The first one we see gets special treatment.
+ for (ResolvedComponentInfo info : currentResolveList) {
+ if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) {
+ mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
+ info.getResolveInfoAt(0),
+ info.getResolveInfoAt(0).loadLabel(mPm),
+ info.getResolveInfoAt(0).loadLabel(mPm),
+ getReplacementIntent(info.getResolveInfoAt(0).activityInfo,
+ info.getIntentAt(0)));
+ currentResolveList.remove(info);
+ break;
+ }
+ }
+
+ if (mOtherProfile == null) {
+ try {
+ mLastChosen = mResolverListController.getLastChosen();
+ } catch (RemoteException re) {
+ Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
+ }
+ }
+
int N;
if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
// We only care about fixing the unfilteredList if the current resolve list and
@@ -1402,19 +1452,7 @@
}
};
sortingTask.execute(currentResolveList);
- if (mPostListBuildRunnable == null) {
- mPostListBuildRunnable = new Runnable() {
- @Override
- public void run() {
- setTitleAndIcon();
- resetAlwaysOrOnceButtonBar();
- onListRebuilt();
- disableLastChosenIfNeeded();
- mPostListBuildRunnable = null;
- }
- };
- getMainThreadHandler().post(mPostListBuildRunnable);
- }
+ postListReadyRunnable();
return false;
} else {
processSortedList(currentResolveList);
@@ -1426,16 +1464,6 @@
}
}
- private void disableLastChosenIfNeeded() {
- // Layout doesn't handle both profile button and last chosen
- // so disable last chosen if profile button is present.
- if (mOtherProfile != null && mLastChosenPosition >= 0) {
- mLastChosenPosition = -1;
- mFilterLastUsed = false;
- }
- }
-
-
private void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
int N;
if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
@@ -1503,10 +1531,27 @@
processGroup(sortedComponents, start, (N - 1), rci0, r0Label);
}
- setTitleAndIcon();
- resetAlwaysOrOnceButtonBar();
- disableLastChosenIfNeeded();
- onListRebuilt();
+ postListReadyRunnable();
+ }
+
+ /**
+ * Some necessary methods for creating the list are initiated in onCreate and will also
+ * determine the layout known. We therefore can't update the UI inline and post to the
+ * handler thread to update after the current task is finished.
+ */
+ private void postListReadyRunnable() {
+ if (mPostListReadyRunnable == null) {
+ mPostListReadyRunnable = new Runnable() {
+ @Override
+ public void run() {
+ setTitleAndIcon();
+ resetAlwaysOrOnceButtonBar();
+ onListRebuilt();
+ mPostListReadyRunnable = null;
+ }
+ };
+ getMainThreadHandler().post(mPostListReadyRunnable);
+ }
}
public void onListRebuilt() {
@@ -1588,6 +1633,11 @@
}
private void updateLastChosenPosition(ResolveInfo info) {
+ // If another profile is present, ignore the last chosen entry.
+ if (mOtherProfile != null) {
+ mLastChosenPosition = -1;
+ return;
+ }
if (mLastChosen != null
&& mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
&& mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
@@ -1595,12 +1645,10 @@
}
}
+ // We assume that at this point we've already filtered out the only intent for a different
+ // targetUserId which we're going to use.
private void addResolveInfo(DisplayResolveInfo dri) {
- if (dri.mResolveInfo.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) {
- // So far we only support a single other profile at a time.
- // The first one we see gets special treatment.
- mOtherProfile = dri;
- } else {
+ if (dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) {
mDisplayList.add(dri);
}
}
@@ -1841,7 +1889,8 @@
final int checkedPos = mAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
- if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
+ if (!useLayoutWithDefault()
+ && (!hasValidSelection || mLastSelected != checkedPos)) {
setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index d82a211..f27c0d4 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -55,8 +55,15 @@
}
@Override
- protected void maybePreload() {
- // Do nothing, we don't need to call ZygoteInit.maybePreload() for the WebView zygote.
+ protected void preload() {
+ // Nothing to preload by default.
+ }
+
+ @Override
+ protected boolean isPreloadComplete() {
+ // Webview zygotes don't preload any classes or resources or defaults, all of their
+ // preloading is package specific.
+ return true;
}
@Override
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 59416dd..fa71a62 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -173,6 +173,10 @@
VM_HOOKS.postForkChild(debugFlags, isSystemServer, instructionSet);
}
+ /**
+ * Resets this process' priority to the default value (0).
+ */
+ native static void nativeResetNicePriority();
/**
* Executes "/system/bin/sh -c <command>" using the exec() system call.
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index a7f311b..e2485e9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -171,7 +171,9 @@
return handleAbiListQuery();
}
- maybePreload();
+ if (parsedArgs.preloadDefault) {
+ return handlePreload();
+ }
if (parsedArgs.preloadPackage != null) {
return handlePreloadPackage(parsedArgs.preloadPackage,
@@ -282,8 +284,34 @@
}
}
- protected void maybePreload() {
- ZygoteInit.maybePreload();
+ /**
+ * Preloads resources if the zygote is in lazily preload mode. Writes the result of the
+ * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1}
+ * if no preload was initiated. The latter implies that the zygote is not configured to load
+ * resources lazy or that the zygote has already handled a previous request to handlePreload.
+ */
+ private boolean handlePreload() {
+ try {
+ if (isPreloadComplete()) {
+ mSocketOutStream.writeInt(1);
+ } else {
+ preload();
+ mSocketOutStream.writeInt(0);
+ }
+
+ return false;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Error writing to command socket", ioe);
+ return true;
+ }
+ }
+
+ protected void preload() {
+ ZygoteInit.lazyPreload();
+ }
+
+ protected boolean isPreloadComplete() {
+ return ZygoteInit.isPreloadComplete();
}
protected boolean handlePreloadPackage(String packagePath, String libsPath) {
@@ -402,6 +430,13 @@
String preloadPackageLibs;
/**
+ * Whether this is a request to start preloading the default resources and classes.
+ * This argument only makes sense when the zygote is in lazy preload mode (i.e, when
+ * it's started with --enable-lazy-preload).
+ */
+ boolean preloadDefault;
+
+ /**
* Constructs instance and parses args
* @param args zygote command-line args
* @throws IllegalArgumentException
@@ -564,6 +599,8 @@
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
+ } else if (arg.equals("--preload-default")) {
+ preloadDefault = true;
} else {
break;
}
@@ -578,7 +615,7 @@
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
- } else {
+ } else if (!preloadDefault) {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a72b66a..310cbc7 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -51,6 +51,7 @@
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;
@@ -146,11 +147,11 @@
sPreloadComplete = true;
}
- public static void maybePreload() {
- if (!sPreloadComplete) {
- Log.i(TAG, "Lazily preloading resources.");
- preload(new BootTimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK));
- }
+ public static void lazyPreload() {
+ Preconditions.checkState(!sPreloadComplete);
+ Log.i(TAG, "Lazily preloading resources.");
+
+ preload(new BootTimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK));
}
private static void beginIcuCachePinning() {
@@ -583,6 +584,7 @@
OsConstants.CAP_NET_RAW,
OsConstants.CAP_SYS_MODULE,
OsConstants.CAP_SYS_NICE,
+ OsConstants.CAP_SYS_PTRACE,
OsConstants.CAP_SYS_RESOURCE,
OsConstants.CAP_SYS_TIME,
OsConstants.CAP_SYS_TTY_CONFIG,
@@ -712,6 +714,8 @@
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
+ } else {
+ Zygote.nativeResetNicePriority();
}
// Finish profiling the zygote initialization.
@@ -783,6 +787,10 @@
}
}
+ static boolean isPreloadComplete() {
+ return sPreloadComplete;
+ }
+
/**
* Class not instantiable.
*/
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 84195b2..804bd29 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3022,10 +3022,14 @@
@Override
public void onSwipeCancelled(SwipeDismissLayout layout) {
WindowManager.LayoutParams newParams = getAttributes();
- newParams.x = 0;
- newParams.alpha = 1;
- setAttributes(newParams);
- setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ // Swipe changes only affect the x-translation and alpha, check to see if
+ // those values have changed first before resetting them.
+ if (newParams.x != 0 || newParams.alpha != 1) {
+ newParams.x = 0;
+ newParams.alpha = 1;
+ setAttributes(newParams);
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ }
}
});
}
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 0fe580a..44b21b4 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -303,17 +303,17 @@
return color;
}
- double[] lab = new double[3];
- ColorUtilsFromCompat.colorToLAB(findFg ? fg : bg, lab);
+ float[] hsl = new float[3];
+ ColorUtilsFromCompat.colorToHSL(findFg ? fg : bg, hsl);
- double low = lab[0], high = 100;
- final double a = lab[1], b = lab[2];
+ float low = hsl[2], high = 1;
for (int i = 0; i < 15 && high - low > 0.00001; i++) {
- final double l = (low + high) / 2;
+ final float l = (low + high) / 2;
+ hsl[2] = l;
if (findFg) {
- fg = ColorUtilsFromCompat.LABToColor(l, a, b);
+ fg = ColorUtilsFromCompat.HSLToColor(hsl);
} else {
- bg = ColorUtilsFromCompat.LABToColor(l, a, b);
+ bg = ColorUtilsFromCompat.HSLToColor(hsl);
}
if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) {
high = l;
@@ -321,7 +321,7 @@
low = l;
}
}
- return ColorUtilsFromCompat.LABToColor(high, a, b);
+ return findFg ? fg : bg;
}
public static int ensureTextContrastOnBlack(int color) {
@@ -456,7 +456,10 @@
}
}
- public static int resolveActionBarColor(int backgroundColor) {
+ public static int resolveActionBarColor(Context context, int backgroundColor) {
+ if (backgroundColor == Notification.COLOR_DEFAULT) {
+ return context.getColor(com.android.internal.R.color.notification_action_list);
+ }
boolean useDark = shouldUseDark(backgroundColor);
final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
ColorUtilsFromCompat.colorToLAB(backgroundColor, result);
@@ -764,6 +767,10 @@
return amount < low ? low : (amount > high ? high : amount);
}
+ private static float constrain(float amount, float low, float high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
private static double pivotXyzComponent(double component) {
return component > XYZ_EPSILON
? Math.pow(component, 1 / 3.0)
@@ -779,5 +786,139 @@
return result;
}
+ /**
+ * Convert HSL (hue-saturation-lightness) components to a RGB color.
+ * <ul>
+ * <li>hsl[0] is Hue [0 .. 360)</li>
+ * <li>hsl[1] is Saturation [0...1]</li>
+ * <li>hsl[2] is Lightness [0...1]</li>
+ * </ul>
+ * If hsv values are out of range, they are pinned.
+ *
+ * @param hsl 3-element array which holds the input HSL components
+ * @return the resulting RGB color
+ */
+ @ColorInt
+ public static int HSLToColor(@NonNull float[] hsl) {
+ final float h = hsl[0];
+ final float s = hsl[1];
+ final float l = hsl[2];
+
+ final float c = (1f - Math.abs(2 * l - 1f)) * s;
+ final float m = l - 0.5f * c;
+ final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
+
+ final int hueSegment = (int) h / 60;
+
+ int r = 0, g = 0, b = 0;
+
+ switch (hueSegment) {
+ case 0:
+ r = Math.round(255 * (c + m));
+ g = Math.round(255 * (x + m));
+ b = Math.round(255 * m);
+ break;
+ case 1:
+ r = Math.round(255 * (x + m));
+ g = Math.round(255 * (c + m));
+ b = Math.round(255 * m);
+ break;
+ case 2:
+ r = Math.round(255 * m);
+ g = Math.round(255 * (c + m));
+ b = Math.round(255 * (x + m));
+ break;
+ case 3:
+ r = Math.round(255 * m);
+ g = Math.round(255 * (x + m));
+ b = Math.round(255 * (c + m));
+ break;
+ case 4:
+ r = Math.round(255 * (x + m));
+ g = Math.round(255 * m);
+ b = Math.round(255 * (c + m));
+ break;
+ case 5:
+ case 6:
+ r = Math.round(255 * (c + m));
+ g = Math.round(255 * m);
+ b = Math.round(255 * (x + m));
+ break;
+ }
+
+ r = constrain(r, 0, 255);
+ g = constrain(g, 0, 255);
+ b = constrain(b, 0, 255);
+
+ return Color.rgb(r, g, b);
+ }
+
+ /**
+ * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
+ * <ul>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
+ * </ul>
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outHsl 3-element array which holds the resulting HSL components
+ */
+ public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
+ RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
+ }
+
+ /**
+ * Convert RGB components to HSL (hue-saturation-lightness).
+ * <ul>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outHsl 3-element array which holds the resulting HSL components
+ */
+ public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull float[] outHsl) {
+ final float rf = r / 255f;
+ final float gf = g / 255f;
+ final float bf = b / 255f;
+
+ final float max = Math.max(rf, Math.max(gf, bf));
+ final float min = Math.min(rf, Math.min(gf, bf));
+ final float deltaMaxMin = max - min;
+
+ float h, s;
+ float l = (max + min) / 2f;
+
+ if (max == min) {
+ // Monochromatic
+ h = s = 0f;
+ } else {
+ if (max == rf) {
+ h = ((gf - bf) / deltaMaxMin) % 6f;
+ } else if (max == gf) {
+ h = ((bf - rf) / deltaMaxMin) + 2f;
+ } else {
+ h = ((rf - gf) / deltaMaxMin) + 4f;
+ }
+
+ s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
+ }
+
+ h = (h * 60f) % 360f;
+ if (h < 0) {
+ h += 360f;
+ }
+
+ outHsl[0] = constrain(h, 0f, 360f);
+ outHsl[1] = constrain(s, 0f, 1f);
+ outHsl[2] = constrain(l, 0f, 1f);
+ }
+
}
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b380b13..b8c062e 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -45,4 +45,10 @@
void systemReady();
void userPresent(int userId);
int getStrongAuthForUser(int userId);
+
+ long addEscrowToken(in byte[] token, int userId);
+ boolean removeEscrowToken(long handle, int userId);
+ boolean isEscrowTokenActive(long handle, int userId);
+ boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId);
+ void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ef6e6c2..0aba9c2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -147,6 +147,10 @@
public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+ public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
+
+ public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
+ public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -769,7 +773,7 @@
getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
userHandle);
- addEncryptionPassword(password, computedQuality, userHandle);
+ updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle);
updatePasswordHistory(password, userHandle);
} catch (RemoteException re) {
// Cant do much
@@ -777,7 +781,11 @@
}
}
- private void addEncryptionPassword(String password, int quality, int userHandle) {
+ /**
+ * Update device encryption password if calling user is USER_SYSTEM and device supports
+ * encryption.
+ */
+ private void updateEncryptionPasswordIfNeeded(String password, int quality, int userHandle) {
// Update the device encryption password.
if (userHandle == UserHandle.USER_SYSTEM
&& LockPatternUtils.isDeviceEncryptionEnabled()) {
@@ -1398,6 +1406,104 @@
}
/**
+ * Create an escrow token for the current user, which can later be used to unlock FBE
+ * or change user password.
+ *
+ * After adding, if the user currently has lockscreen password, he will need to perform a
+ * confirm credential operation in order to activate the token for future use. If the user
+ * has no secure lockscreen, then the token is activated immediately.
+ *
+ * @return a unique 64-bit token handle which is needed to refer to this token later.
+ */
+ public long addEscrowToken(byte[] token, int userId) {
+ try {
+ return getLockSettings().addEscrowToken(token, userId);
+ } catch (RemoteException re) {
+ return 0L;
+ }
+ }
+
+ /**
+ * Remove an escrow token.
+ * @return true if the given handle refers to a valid token previously returned from
+ * {@link #addEscrowToken}, whether it's active or not. return false otherwise.
+ */
+ public boolean removeEscrowToken(long handle, int userId) {
+ try {
+ return getLockSettings().removeEscrowToken(handle, userId);
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+
+ /**
+ * Check if the given escrow token is active or not. Only active token can be used to call
+ * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
+ */
+ public boolean isEscrowTokenActive(long handle, int userId) {
+ try {
+ return getLockSettings().isEscrowTokenActive(handle, userId);
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+
+ public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
+ byte[] token, int userId) {
+ try {
+ if (type != CREDENTIAL_TYPE_NONE) {
+ if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
+ throw new IllegalArgumentException("password must not be null and at least "
+ + "of length " + MIN_LOCK_PASSWORD_SIZE);
+ }
+
+ final int computedQuality = PasswordMetrics.computeForPassword(credential).quality;
+ if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle,
+ token, userId)) {
+ return false;
+ }
+ setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ computedQuality), userId);
+
+ updateEncryptionPasswordIfNeeded(credential, computedQuality, userId);
+ updatePasswordHistory(credential, userId);
+ } else {
+ if (!TextUtils.isEmpty(credential)) {
+ throw new IllegalArgumentException("password must be emtpy for NONE type");
+ }
+ if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE,
+ tokenHandle, token, userId)) {
+ return false;
+ }
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+
+ if (userId == UserHandle.USER_SYSTEM) {
+ // Set the encryption password to default.
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+ setCredentialRequiredToDecrypt(false);
+ }
+ }
+ onAfterChangingPassword(userId);
+ return true;
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to save lock password ", re);
+ re.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) {
+ try {
+ getLockSettings().unlockUserWithToken(tokenHandle, token, userId);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unlock user with token", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
* Callback to be notified about progress when checking credentials.
*/
public interface CheckCredentialProgressCallback {
@@ -1559,6 +1665,14 @@
break;
}
}
- };
+ }
+ }
+
+ public void enableSyntheticPassword() {
+ setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1L, UserHandle.USER_SYSTEM);
+ }
+
+ public boolean isSyntheticPasswordEnabled() {
+ return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
}
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 261fa43..6d814bf 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -79,7 +79,6 @@
private boolean mDismissed;
private boolean mDiscardIntercept;
private VelocityTracker mVelocityTracker;
- private float mTranslationX;
private boolean mBlockGesture = false;
private boolean mActivityTranslucencyConverted = false;
@@ -166,8 +165,10 @@
return super.onInterceptTouchEvent(ev);
}
- // offset because the view is translated during swipe
- ev.offsetLocation(mTranslationX, 0);
+ // Offset because the view is translated during swipe, match X with raw X. Active touch
+ // coordinates are mostly used by the velocity tracker, so offset it to match the raw
+ // coordinates which is what is primarily used elsewhere.
+ ev.offsetLocation(ev.getRawX() - ev.getX(), 0);
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -232,8 +233,12 @@
if (mVelocityTracker == null || !mDismissable) {
return super.onTouchEvent(ev);
}
- // offset because the view is translated during swipe
- ev.offsetLocation(mTranslationX, 0);
+
+ // Offset because the view is translated during swipe, match X with raw X. Active touch
+ // coordinates are mostly used by the velocity tracker, so offset it to match the raw
+ // coordinates which is what is primarily used elsewhere.
+ ev.offsetLocation(ev.getRawX() - ev.getX(), 0);
+
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_UP:
updateDismiss(ev);
@@ -266,7 +271,6 @@
}
private void setProgress(float deltaX) {
- mTranslationX = deltaX;
if (mProgressListener != null && deltaX >= 0) {
mProgressListener.onSwipeProgressChanged(
this, progressToAlpha(deltaX / getWidth()), deltaX);
@@ -300,7 +304,6 @@
mVelocityTracker.recycle();
}
mVelocityTracker = null;
- mTranslationX = 0;
mDownX = 0;
mLastX = Integer.MIN_VALUE;
mDownY = 0;
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index fcb4c7b..3d012bf 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -109,6 +109,10 @@
// background while in data-usage save mode, as read from the configuration files.
final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>();
+ // These are the packages that are white-listed to be able to run background location
+ // without throttling, as read from the configuration files.
+ final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
+
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -182,6 +186,10 @@
return mAllowInDataUsageSave;
}
+ public ArraySet<String> getAllowUnthrottledLocation() {
+ return mAllowUnthrottledLocation;
+ }
+
public ArraySet<String> getLinkedApps() {
return mLinkedApps;
}
@@ -446,6 +454,17 @@
XmlUtils.skipCurrentTag(parser);
continue;
+ } else if ("allow-unthrottled-location".equals(name) && allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<allow-unthrottled-location> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowUnthrottledLocation.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
} else if ("allow-implicit-broadcast".equals(name) && allowAll) {
String action = parser.getAttributeValue(null, "action");
if (action == null) {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index c49287c..c261e41 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -27,6 +27,7 @@
#include "Bitmap.h"
#include "SkDrawFilter.h"
#include "SkGraphics.h"
+#include "SkRegion.h"
namespace android {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index b2c8168..2cfaeeb 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -37,6 +37,7 @@
#include "android_media_AudioErrors.h"
#include "android_media_PlaybackParams.h"
#include "android_media_DeviceCallback.h"
+#include "android_media_VolumeShaper.h"
#include <cinttypes>
@@ -64,6 +65,7 @@
static audio_track_fields_t javaAudioTrackFields;
static audio_attributes_fields_t javaAudioAttrFields;
static PlaybackParams::fields_t gPlaybackParamsFields;
+static VolumeShaperHelper::fields_t gVolumeShaperFields;
struct audiotrack_callback_cookie {
jclass audioTrack_class;
@@ -1178,6 +1180,50 @@
return FCC_8;
}
+// Pass through the arguments to the AudioFlinger track implementation.
+static jint android_media_AudioTrack_apply_volume_shaper(JNIEnv *env, jobject thiz,
+ jobject jconfig, jobject joperation) {
+ // NOTE: hard code here to prevent platform issues. Must match VolumeShaper.java
+ const int VOLUME_SHAPER_INVALID_OPERATION = -38;
+
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == nullptr) {
+ return (jint)VOLUME_SHAPER_INVALID_OPERATION;
+ }
+
+ sp<VolumeShaper::Configuration> configuration;
+ sp<VolumeShaper::Operation> operation;
+ if (jconfig != nullptr) {
+ configuration = VolumeShaperHelper::convertJobjectToConfiguration(
+ env, gVolumeShaperFields, jconfig);
+ ALOGV("applyVolumeShaper configuration: %s", configuration->toString().c_str());
+ }
+ if (joperation != nullptr) {
+ operation = VolumeShaperHelper::convertJobjectToOperation(
+ env, gVolumeShaperFields, joperation);
+ ALOGV("applyVolumeShaper operation: %s", operation->toString().c_str());
+ }
+ VolumeShaper::Status status = lpTrack->applyVolumeShaper(configuration, operation);
+ if (status == INVALID_OPERATION) {
+ status = VOLUME_SHAPER_INVALID_OPERATION;
+ }
+ return (jint)status; // if status < 0 an error, else a VolumeShaper id
+}
+
+// Pass through the arguments to the AudioFlinger track implementation.
+static jobject android_media_AudioTrack_get_volume_shaper_state(JNIEnv *env, jobject thiz,
+ jint id) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == nullptr) {
+ return (jobject)nullptr;
+ }
+
+ sp<VolumeShaper::State> state = lpTrack->getVolumeShaperState((int)id);
+ if (state.get() == nullptr) {
+ return (jobject)nullptr;
+ }
+ return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state);
+}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
@@ -1242,6 +1288,12 @@
{"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback},
{"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback},
{"native_get_FCC_8", "()I", (void *)android_media_AudioTrack_get_FCC_8},
+ {"native_applyVolumeShaper",
+ "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
+ (void *)android_media_AudioTrack_apply_volume_shaper},
+ {"native_getVolumeShaperState",
+ "(I)Landroid/media/VolumeShaper$State;",
+ (void *)android_media_AudioTrack_get_volume_shaper_state},
};
@@ -1312,6 +1364,7 @@
// initialize PlaybackParams field info
gPlaybackParamsFields.init(env);
+ gVolumeShaperFields.init(env);
return res;
}
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp
index 65c1590..d934870 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_HardwareLayer.cpp
@@ -66,10 +66,10 @@
}
static void android_view_HardwareLayer_setSurfaceTexture(JNIEnv* env, jobject clazz,
- jlong layerUpdaterPtr, jobject surface, jboolean isAlreadyAttached) {
+ jlong layerUpdaterPtr, jobject surface) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
sp<GLConsumer> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
- layer->setSurfaceTexture(surfaceTexture, !isAlreadyAttached);
+ layer->setSurfaceTexture(surfaceTexture);
}
static void android_view_HardwareLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz,
@@ -88,7 +88,7 @@
{ "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare },
{ "nSetLayerPaint", "(JJ)V", (void*) android_view_HardwareLayer_setLayerPaint },
{ "nSetTransform", "(JJ)V", (void*) android_view_HardwareLayer_setTransform },
- { "nSetSurfaceTexture", "(JLandroid/graphics/SurfaceTexture;Z)V",
+ { "nSetSurfaceTexture", "(JLandroid/graphics/SurfaceTexture;)V",
(void*) android_view_HardwareLayer_setSurfaceTexture },
{ "nUpdateSurfaceTexture", "(J)V", (void*) android_view_HardwareLayer_updateSurfaceTexture },
};
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e2fc444..c3f0e9d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -806,6 +806,10 @@
UnmountTree("/storage");
}
+static void com_android_internal_os_Zygote_nativeResetNicePriority(JNIEnv* env, jclass) {
+ ResetNicePriority(env);
+}
+
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
@@ -815,7 +819,9 @@
{ "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
{ "nativeUnmountStorageOnInit", "()V",
- (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }
+ (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit },
+ { "nativeResetNicePriority", "()V",
+ (void *) com_android_internal_os_Zygote_nativeResetNicePriority }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index bc257e0..819460e 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -23,6 +23,8 @@
message NotificationServiceDumpProto {
repeated NotificationRecordProto records = 1;
+
+ ZenModeProto zen = 2;
}
message NotificationRecordProto {
@@ -42,4 +44,21 @@
ENQUEUED = 0;
POSTED = 1;
+
+ SNOOZED = 2;
+}
+
+message ZenModeProto {
+ ZenMode zen_mode = 1;
+ repeated string enabled_active_conditions = 2;
+ int32 suppressed_effects = 3;
+ repeated string suppressors = 4;
+ string policy = 5;
+}
+
+enum ZenMode {
+ ZEN_MODE_OFF = 0;
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+ ZEN_MODE_NO_INTERRUPTIONS = 2;
+ ZEN_MODE_ALARMS = 3;
}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cd02fbb..97fbfa5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1813,6 +1813,22 @@
android:description="@string/permdesc_systemAlertWindow"
android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+ <!-- Allows an app to run in the background.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.RUN_IN_BACKGROUND"
+ android:label="@string/permlab_runInBackground"
+ android:description="@string/permdesc_runInBackground"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to use data in the background.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.USE_DATA_IN_BACKGROUND"
+ android:label="@string/permlab_useDataInBackground"
+ android:description="@string/permdesc_useDataInBackground"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
<!-- ================================== -->
@@ -2996,7 +3012,7 @@
any metadata and intents attached.
@hide -->
<permission android:name="android.permission.ACCESS_NOTIFICATIONS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|appop" />
<!-- Marker permission for applications that wish to access notification policy.
<p>Protection level: normal
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d8fc1d1..4d5e45b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5846,8 +5846,6 @@
<!-- Drawable used to draw masked icons with foreground and background layers. -->
<declare-styleable name="MaskableIconDrawableLayer">
- <!-- The color to use for the layer, only if drawable is not defined. -->
- <attr name="color" />
<!-- The drawable to use for the layer. -->
<attr name="drawable" />
</declare-styleable>
@@ -8507,6 +8505,12 @@
<attr name="fontWeight" format="integer" />
</declare-styleable>
+ <!-- Attributes that are read when parsing a <fontfamily> tag, -->
+ <declare-styleable name="FontFamily">
+ <attr name="fontProviderAuthority" format="string" />
+ <attr name="fontProviderQuery" format="string" />
+ </declare-styleable>
+
<!-- @hide -->
<declare-styleable name="RecyclerView">
<attr name="layoutManager" format="string" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 54c392f..fcb0e08 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1286,6 +1286,16 @@
<item>com.android.networkrecommendation</item>
</string-array>
+ <!-- The package name of the default network recommendation app.
+ A network recommendation provider must:
+ * Be granted the SCORE_NETWORKS permission.
+ * Include a Service for the android.net.scoring.RECOMMEND_NETWORKS action
+ protected by the BIND_NETWORK_RECOMMENDATION_SERVICE permission.
+
+ This must be set to a valid network recommendation app.
+ -->
+ <string name="config_defaultNetworkRecommendationProviderPackage" translatable="false">com.android.networkrecommendation</string>
+
<!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be
replaced by an app at run-time. When disabled, only the
config_hardwareFlpPackageName package will be searched for Hardware Flp,
@@ -2541,7 +2551,7 @@
<!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
These values are in DPs and will be converted to pixel sizes internally. -->
- <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">8x8</string>
+ <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
<!-- Max default size [WIDTHxHEIGHT] on screen for picture-in-picture windows to fit inside.
These values are in DPs and will be converted to pixel sizes internally. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 34659aa..78489eb 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2794,6 +2794,8 @@
<public name="canCaptureFingerprintGestures" />
<public name="alphabeticModifiers" />
<public name="numericModifiers" />
+ <public name="fontProviderAuthority" />
+ <public name="fontProviderQuery" />
</public-group>
<public-group type="style" first-id="0x010302e0">
@@ -2806,6 +2808,7 @@
<public type="attr" name="primaryContentAlpha" />
<public type="attr" name="secondaryContentAlpha" />
+
<!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2309866..8faa76c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -816,6 +816,16 @@
<string name="permdesc_systemAlertWindow">This app can appear on top of other apps or other parts of the screen. This may interfere with normal app usage and change the way that other apps appear.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_runInBackground">run in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_runInBackground">This app can run in the background. This may drain battery faster.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_useDataInBackground">use data in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_persistentActivity">make app always run</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1324e38..0d63a1e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2790,6 +2790,7 @@
<!-- Network Recommendation -->
<java-symbol type="array" name="config_networkRecommendationPackageNames" />
+ <java-symbol type="string" name="config_defaultNetworkRecommendationProviderPackage" />
<!-- Whether allow 3rd party apps on internal storage. -->
<java-symbol type="bool" name="config_allow3rdPartyAppOnInternal" />
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfont.xml b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
new file mode 100644
index 0000000..35391bd
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fontProviderAuthority="com.example.test.fontprovider"
+ android:fontProviderQuery="MyRequestedFont">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 380a28774..8b536a7 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -15,6 +15,8 @@
*/
package android.content.res;
+import static junit.framework.Assert.assertNull;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -78,4 +80,19 @@
assertEquals(true, font4.isItalic());
assertEquals("res/font/samplefont4.ttf", font4.getFontName());
}
+
+ @Test
+ public void testParseDownloadableFont() throws IOException, XmlPullParserException {
+ XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfont);
+
+ FontConfig result = FontResourcesParser.parse(parser, mResources);
+
+ assertNotNull(result);
+ List<FontConfig.Family> families = result.getFamilies();
+ assertEquals(1, families.size());
+ FontConfig.Family family = families.get(0);
+ assertEquals("com.example.test.fontprovider", family.getProviderAuthority());
+ assertEquals("MyRequestedFont", family.getQuery());
+ assertNull(family.getFonts());
+ }
}
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index 1afe9da..fff23a0 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -4,6 +4,7 @@
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiSsid;
import android.support.test.runner.AndroidJUnit4;
@@ -17,7 +18,9 @@
@RunWith(AndroidJUnit4.class)
public class NetworkKeyTest {
private static final String VALID_SSID = "\"ssid1\"";
+ private static final String VALID_UNQUOTED_SSID = "ssid1";
private static final String VALID_BSSID = "00:00:00:00:00:00";
+ private static final String INVALID_BSSID = "invalid_bssid";
@Mock private WifiInfo mWifiInfo;
@Before
@@ -64,6 +67,13 @@
}
@Test
+ public void createFromWifi_invalidBssid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ when(mWifiInfo.getBSSID()).thenReturn(INVALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
public void createFromWifi_validWifiInfo() throws Exception {
when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
@@ -72,4 +82,72 @@
final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo);
assertEquals(expected, actual);
}
+
+ @Test
+ public void createFromScanResult_nullInput() {
+ assertNull(NetworkKey.createFromScanResult(null));
+ }
+
+ @Test
+ public void createFromScanResult_nullWifiSsid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.BSSID = VALID_BSSID;
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_emptyWifiSsid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded("");
+ scanResult.BSSID = VALID_BSSID;
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_noneWifiSsid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(WifiSsid.NONE);
+ scanResult.BSSID = VALID_BSSID;
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_nullBssid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID);
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_emptyBssid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID);
+ scanResult.BSSID = "";
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_invalidBssid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID);
+ scanResult.BSSID = INVALID_BSSID;
+
+ assertNull(NetworkKey.createFromScanResult(scanResult));
+ }
+
+ @Test
+ public void createFromScanResult_validWifiSsid() {
+ ScanResult scanResult = new ScanResult();
+ scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(VALID_UNQUOTED_SSID);
+ scanResult.BSSID = VALID_BSSID;
+
+ NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID));
+ NetworkKey actual = NetworkKey.createFromScanResult(scanResult);
+ assertEquals(expected, actual);
+ }
}
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index a260e94..864b48a 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -37,23 +37,12 @@
// Sync the state to the TextView and call onKeyDown with KEYCODE_DEL key event.
// Then update the state to the result of TextView.
private void backspace(final EditorState state, int modifiers) {
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- mTextView.setText(state.mText, BufferType.EDITABLE);
- mTextView.setKeyListener(mKeyListener);
- mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd);
- }
- });
- mInstrumentation.waitForIdleSync();
- assertTrue(mTextView.hasWindowFocus());
+ mTextView.setText(state.mText, BufferType.EDITABLE);
+ mTextView.setKeyListener(mKeyListener);
+ mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd);
final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_DEL, modifiers);
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent);
- }
- });
- mInstrumentation.waitForIdleSync();
+ mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent);
state.mText = mTextView.getText();
state.mSelectionStart = mTextView.getSelectionStart();
@@ -247,6 +236,51 @@
state.assertEquals("U+1F1FA U+1F1F8 |");
backspace(state, 0);
state.assertEquals("|");
+
+ // Incomplete sequence. (no tag_term: U+E007E)
+ state.setByString("'a' U+1F3F4 U+E0067 'b' |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+1F3F4 U+E0067 |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+1F3F4 |");
+ backspace(state, 0);
+ state.assertEquals("'a' |");
+
+ // No tag_base
+ state.setByString("'a' U+E0067 U+E007F 'b' |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E0067 U+E007F |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E0067 |");
+ backspace(state, 0);
+ state.assertEquals("'a' |");
+
+ // Isolated tag chars
+ state.setByString("'a' U+E0067 U+E0067 'b' |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E0067 U+E0067 |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E0067 |");
+ backspace(state, 0);
+ state.assertEquals("'a' |");
+
+ // Isolated tab term.
+ state.setByString("'a' U+E007F U+E007F 'b' |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E007F U+E007F |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+E007F |");
+ backspace(state, 0);
+ state.assertEquals("'a' |");
+
+ // Immediate tag_term after tag_base
+ state.setByString("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b' |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F |");
+ backspace(state, 0);
+ state.assertEquals("'a' U+1F3F4 U+E007F |");
+ backspace(state, 0);
+ state.assertEquals("'a' |");
}
@SmallTest
diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
index 1990fd0..839d380 100644
--- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
+++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
@@ -37,23 +37,12 @@
// Sync the state to the TextView and call onKeyDown with KEYCODE_FORWARD_DEL key event.
// Then update the state to the result of TextView.
private void forwardDelete(final EditorState state, int modifiers) {
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- mTextView.setText(state.mText, BufferType.EDITABLE);
- mTextView.setKeyListener(mKeyListener);
- mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd);
- }
- });
- mInstrumentation.waitForIdleSync();
- assertTrue(mTextView.hasWindowFocus());
+ mTextView.setText(state.mText, BufferType.EDITABLE);
+ mTextView.setKeyListener(mKeyListener);
+ mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd);
final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, modifiers);
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent);
- }
- });
- mInstrumentation.waitForIdleSync();
+ mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent);
state.mText = mTextView.getText();
state.mSelectionStart = mTextView.getSelectionStart();
@@ -186,6 +175,46 @@
state.assertEquals("| U+1F1FA");
forwardDelete(state, 0);
state.assertEquals("|");
+
+ // Incomplete sequence. (no tag_term:U+E007E)
+ state.setByString("| 'a' U+1F3F4 U+E0067 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| U+1F3F4 U+E0067 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
+
+ // No tag_base
+ state.setByString("| 'a' U+E0067 U+E007F 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
+
+ // Isolated tag chars
+ state.setByString("| 'a' U+E0067 U+E0067 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
+
+ // Isolated tag base.
+ state.setByString("| 'a' U+1F3F4 U+1F3F4 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| U+1F3F4 U+1F3F4 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| U+1F3F4 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
+
+ // Isolated tab term.
+ state.setByString("| 'a' U+E007F U+E007F 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
+
+ // Immediate tag_term after tag_base
+ state.setByString("| 'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| U+1F3F4 U+E007F 'b'");
+ forwardDelete(state, 0);
+ state.assertEquals("| 'b'");
}
@SmallTest
diff --git a/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java b/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java
index 4b4e7af..f005d7b 100644
--- a/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java
+++ b/core/tests/coretests/src/android/text/method/KeyListenerTestCase.java
@@ -17,41 +17,26 @@
package android.text.method;
import android.app.Instrumentation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.text.format.DateUtils;
+import android.test.InstrumentationTestCase;
import android.view.KeyEvent;
import android.widget.EditText;
-import android.widget.TextViewActivity;
import com.android.frameworks.coretests.R;
-public abstract class KeyListenerTestCase extends
- ActivityInstrumentationTestCase2<TextViewActivity> {
+public abstract class KeyListenerTestCase extends InstrumentationTestCase {
- protected TextViewActivity mActivity;
protected Instrumentation mInstrumentation;
protected EditText mTextView;
public KeyListenerTestCase() {
- super(TextViewActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
- mActivity = getActivity();
mInstrumentation = getInstrumentation();
- mTextView = (EditText) mActivity.findViewById(R.id.textview);
-
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- // Ensure that the screen is on for this test.
- mTextView.setKeepScreenOn(true);
- }
- });
-
- assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS));
+ mTextView = new EditText(mInstrumentation.getContext());
}
protected static KeyEvent getKey(int keycode, int metaState) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index daebf88..1080a9f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -211,6 +211,119 @@
assertThat(activity.isFinishing(), is(true));
}
+ @Test
+ public void hasOtherProfileOneOption() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(1));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasLastChosenActivityAndOtherProfile() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -227,6 +340,19 @@
return infoList;
}
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ if (i == 0) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ } else {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ }
+ return infoList;
+ }
+
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 84b844a..2c23018 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -43,6 +43,7 @@
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
@@ -102,6 +103,7 @@
public void hasLastChosenActivity() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -121,14 +123,133 @@
return true;
};
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- onView(withId(R.id.title)).perform(click());
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasOtherProfileOneOption() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().getCount(), is(1));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
onView(withId(R.id.button_once))
.perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
+ @Test
+ public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+
+ @Test
+ public void hasLastChosenActivityAndOtherProfile() throws Exception {
+ // In this case we prefer the other profile and don't display anything about the last
+ // chosen activity.
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+
+ // Check that the "Other Profile" activity is put in the right spot
+ onView(withId(R.id.profile_button)).check(matches(
+ withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -145,6 +266,19 @@
return infoList;
}
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ if (i == 0) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ } else {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ }
+ return infoList;
+ }
+
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index ae06306..c710b9a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -32,9 +32,16 @@
*/
class ResolverDataProvider {
+ static private int USER_SOMEONE_ELSE = 10;
+
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
- createResolverIntent(i), createResolveInfo(i));
+ createResolverIntent(i), createResolveInfo(i, UserHandle.USER_CURRENT));
+ }
+
+ static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) {
+ return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
+ createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
}
static ComponentName createComponentName(int i) {
@@ -42,10 +49,10 @@
return new ComponentName("foo.bar." + name, name);
}
- static ResolveInfo createResolveInfo(int i) {
+ static ResolveInfo createResolveInfo(int i, int userId) {
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = createActivityInfo(i);
- resolveInfo.targetUserId = UserHandle.USER_CURRENT;
+ resolveInfo.targetUserId = userId;
return resolveInfo;
}
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
new file mode 100644
index 0000000..c1e8c98
--- /dev/null
+++ b/core/tests/packagemanagertests/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ frameworks-base-testutils
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/packagemanagertests/AndroidManifest.xml b/core/tests/packagemanagertests/AndroidManifest.xml
new file mode 100644
index 0000000..8f49008
--- /dev/null
+++ b/core/tests/packagemanagertests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.coretests.packagemanager"
+ android:sharedUserId="android.uid.system">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.coretests.packagemanager"
+ android:label="Frameworks PackageManager Core Tests" />
+
+</manifest>
diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
new file mode 100644
index 0000000..1097bc7
--- /dev/null
+++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
@@ -0,0 +1,109 @@
+/*
+ * 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.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This test needs to be run without any secondary users on the device,
+ * and selinux needs to be disabled with "adb shell setenforce 0".
+ */
+@RunWith(AndroidJUnit4.class)
+public class KernelPackageMappingTests {
+
+ private static final String TAG = "KernelPackageMapping";
+ private static final String SDCARDFS_PATH = "/config/sdcardfs";
+
+ private UserInfo mSecondaryUser;
+
+ private static File getKernelPackageDir(String packageName) {
+ return new File(new File(SDCARDFS_PATH), packageName);
+ }
+
+ private static File getKernelPackageFile(String packageName, String filename) {
+ return new File(getKernelPackageDir(packageName), filename);
+ }
+
+ private UserManager getUserManager() {
+ UserManager um = (UserManager) InstrumentationRegistry.getContext().getSystemService(
+ Context.USER_SERVICE);
+ return um;
+ }
+
+ private IPackageManager getIPackageManager() {
+ IPackageManager ipm = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ return ipm;
+ }
+
+ private static String getContent(File file) {
+ try {
+ return FileUtils.readTextFile(file, 0, null).trim();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Couldn't read file " + file.getAbsolutePath() + "\n" + ioe);
+ return "<error>";
+ }
+ }
+
+ @Test
+ public void testInstalledPrimary() throws Exception {
+ assertEquals("1000", getContent(getKernelPackageFile("com.android.settings", "appid")));
+ }
+
+ @Test
+ public void testInstalledAll() throws Exception {
+ assertEquals("", getContent(getKernelPackageFile("com.android.settings",
+ "excluded_userids")));
+ }
+
+ @Test
+ public void testNotInstalledSecondary() throws Exception {
+ mSecondaryUser = getUserManager().createUser("Secondary", 0);
+ assertEquals(Integer.toString(mSecondaryUser.id),
+ getContent(
+ getKernelPackageFile("com.android.frameworks.coretests.packagemanager",
+ "excluded_userids")));
+ }
+
+ @After
+ public void shutDown() throws Exception {
+ if (mSecondaryUser != null) {
+ getUserManager().removeUser(mSecondaryUser.id);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 23e7305..7289429 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.os.Build;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -290,11 +291,6 @@
/** @hide */
@IntDef(flag = true,
value = {
- MATRIX_SAVE_FLAG,
- CLIP_SAVE_FLAG,
- HAS_ALPHA_LAYER_SAVE_FLAG,
- FULL_COLOR_LAYER_SAVE_FLAG,
- CLIP_TO_LAYER_SAVE_FLAG,
ALL_SAVE_FLAG
})
@Retention(RetentionPolicy.SOURCE)
@@ -302,21 +298,39 @@
/**
* Restore the current matrix when restore() is called.
+ *
+ * @deprecated Use the flagless version of {@link #save()}, {@link #saveLayer(RectF, Paint)} or
+ * {@link #saveLayerAlpha(RectF, int)}. For saveLayer() calls the matrix
+ * was always restored for {@link #isHardwareAccelerated() Hardware accelerated}
+ * canvases and as of API level {@value Build.VERSION_CODES#O} that is the default
+ * behavior for all canvas types.
*/
public static final int MATRIX_SAVE_FLAG = 0x01;
/**
* Restore the current clip when restore() is called.
+ *
+ * @deprecated Use the flagless version of {@link #save()}, {@link #saveLayer(RectF, Paint)} or
+ * {@link #saveLayerAlpha(RectF, int)}. For saveLayer() calls the clip
+ * was always restored for {@link #isHardwareAccelerated() Hardware accelerated}
+ * canvases and as of API level {@value Build.VERSION_CODES#O} that is the default
+ * behavior for all canvas types.
*/
public static final int CLIP_SAVE_FLAG = 0x02;
/**
* The layer requires a per-pixel alpha channel.
+ *
+ * @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
+ * {@link #saveLayerAlpha(RectF, int)}.
*/
public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04;
/**
* The layer requires full 8-bit precision for each color channel.
+ *
+ * @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
+ * {@link #saveLayerAlpha(RectF, int)}.
*/
public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08;
@@ -326,6 +340,10 @@
* omit this flag for any call to <code>saveLayer()</code> and
* <code>saveLayerAlpha()</code> variants. Not passing this flag generally
* triggers extremely poor performance with hardware accelerated rendering.
+ *
+ * @deprecated This flag results in poor performance and the same effect can be achieved with
+ * a single layer or multiple draw commands with different clips.
+ *
*/
public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
@@ -335,6 +353,9 @@
* strongly recommended to pass this - the complete set of flags - to any
* call to <code>saveLayer()</code> and <code>saveLayerAlpha()</code>
* variants.
+ *
+ * <p class="note"><strong>Note:</strong> all methods that accept this flag
+ * have flagless versions that are equivalent to passing this flag.
*/
public static final int ALL_SAVE_FLAG = 0x1F;
@@ -364,6 +385,7 @@
* restore() is made, those calls will be forgotten, and the settings that
* existed before the save() will be reinstated.
*
+ * @deprecated Use {@link #save()} instead.
* @param saveFlags flag bits that specify which parts of the Canvas state
* to save/restore
* @return The value to pass to restoreToCount() to balance this save()
@@ -394,6 +416,7 @@
* {@link Paint#getColorFilter() ColorFilter} are applied when the
* offscreen bitmap is drawn back when restore() is called.
*
+ * @deprecated Use {@link #saveLayer(RectF, Paint)} instead.
* @param bounds May be null. The maximum size the offscreen bitmap
* needs to be (in local coordinates)
* @param paint This is copied, and is applied to the offscreen when
@@ -410,7 +433,30 @@
}
/**
- * Convenience for saveLayer(bounds, paint, {@link #ALL_SAVE_FLAG})
+ * This behaves the same as save(), but in addition it allocates and
+ * redirects drawing to an offscreen rendering target.
+ * <p class="note"><strong>Note:</strong> this method is very expensive,
+ * incurring more than double rendering cost for contained content. Avoid
+ * using this method when possible and instead use a
+ * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+ * to apply an xfermode, color filter, or alpha, as it will perform much
+ * better than this method.
+ * <p>
+ * All drawing calls are directed to a newly allocated offscreen rendering target.
+ * Only when the balancing call to restore() is made, is that offscreen
+ * buffer drawn back to the current target of the Canvas (which can potentially be a previous
+ * layer if these calls are nested).
+ * <p>
+ * Attributes of the Paint - {@link Paint#getAlpha() alpha},
+ * {@link Paint#getXfermode() Xfermode}, and
+ * {@link Paint#getColorFilter() ColorFilter} are applied when the
+ * offscreen rendering target is drawn back when restore() is called.
+ *
+ * @param bounds May be null. The maximum size the offscreen render target
+ * needs to be (in local coordinates)
+ * @param paint This is copied, and is applied to the offscreen when
+ * restore() is called.
+ * @return value to pass to restoreToCount() to balance this save()
*/
public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) {
return saveLayer(bounds, paint, ALL_SAVE_FLAG);
@@ -418,6 +464,8 @@
/**
* Helper version of saveLayer() that takes 4 values rather than a RectF.
+ *
+ * @deprecated Use {@link #saveLayer(float, float, float, float, Paint)} instead.
*/
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
@Saveflags int saveFlags) {
@@ -427,7 +475,8 @@
}
/**
- * Convenience for saveLayer(left, top, right, bottom, paint, {@link #ALL_SAVE_FLAG})
+ * Convenience for {@link #saveLayer(RectF, Paint)} that takes the four float coordinates of the
+ * bounds rectangle.
*/
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) {
return saveLayer(left, top, right, bottom, paint, ALL_SAVE_FLAG);
@@ -453,6 +502,7 @@
* The {@code alpha} parameter is applied when the offscreen bitmap is
* drawn back when restore() is called.
*
+ * @deprecated Use {@link #saveLayerAlpha(RectF, int)} instead.
* @param bounds The maximum size the offscreen bitmap needs to be
* (in local coordinates)
* @param alpha The alpha to apply to the offscreen when it is
@@ -469,7 +519,13 @@
}
/**
- * Convenience for saveLayerAlpha(bounds, alpha, {@link #ALL_SAVE_FLAG})
+ * Convenience for {@link #saveLayer(RectF, Paint)} but instead of taking a entire Paint object
+ * it takes only the {@code alpha} parameter.
+ *
+ * @param bounds The maximum size the offscreen bitmap needs to be
+ * (in local coordinates)
+ * @param alpha The alpha to apply to the offscreen when it is
+ drawn during restore()
*/
public int saveLayerAlpha(@Nullable RectF bounds, int alpha) {
return saveLayerAlpha(bounds, alpha, ALL_SAVE_FLAG);
@@ -477,6 +533,8 @@
/**
* Helper for saveLayerAlpha() that takes 4 values instead of a RectF.
+ *
+ * @deprecated Use {@link #saveLayerAlpha(float, float, float, float, int)} instead.
*/
public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
@Saveflags int saveFlags) {
@@ -486,7 +544,8 @@
}
/**
- * Helper for saveLayerAlpha(left, top, right, bottom, alpha, {@link #ALL_SAVE_FLAG})
+ * Convenience for {@link #saveLayerAlpha(RectF, int)} that takes the four float coordinates of
+ * the bounds rectangle.
*/
public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) {
return saveLayerAlpha(left, top, right, bottom, alpha, ALL_SAVE_FLAG);
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 750ef3f..7eb8099 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -163,36 +163,54 @@
@Nullable
public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
if (sFallbackFonts != null) {
+ Typeface typeface = findFromCache(mgr, path);
+ if (typeface != null) return typeface;
+
+ List<FontConfig.Family> families = config.getFamilies();
+ if (families == null || families.isEmpty()) {
+ throw new RuntimeException(
+ "Font resource " + path + " contained no font families.");
+ }
+ if (families.size() > 1) {
+ throw new RuntimeException(
+ "Font resource " + path + " contained more than one family.");
+ }
+ FontConfig.Family family = families.get(0);
+ if (family.getProviderAuthority() != null && family.getQuery() != null) {
+ // Downloadable font
+ typeface = findFromCache(
+ family.getProviderAuthority(), family.getQuery());
+ if (typeface != null) {
+ return typeface;
+ }
+ // Downloaded font and it wasn't cached, request it again and return a
+ // default font instead (nothing we can do now).
+ create(new FontRequest(family.getProviderAuthority(), family.getQuery()),
+ NO_OP_REQUEST_CALLBACK);
+ return DEFAULT;
+ }
+
+ FontFamily fontFamily = new FontFamily();
+ List<FontConfig.Font> fonts = family.getFonts();
+ if (fonts == null || fonts.isEmpty()) {
+ throw new RuntimeException("Font resource " + path + " contained no fonts.");
+ }
+ for (int i = 0; i < fonts.size(); i++) {
+ FontConfig.Font font = fonts.get(i);
+ // TODO: Use style and weight info
+ if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
+ 0 /* resourceCookie */, false /* isAsset */)) {
+ return null;
+ }
+ }
+ fontFamily.freeze();
+ FontFamily[] familyChain = { fontFamily };
+ typeface = createFromFamiliesWithDefault(familyChain);
synchronized (sDynamicTypefaceCache) {
final String key = createAssetUid(mgr, path);
- Typeface typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
-
- List<FontConfig.Family> families = config.getFamilies();
- if (families == null || families.isEmpty()) {
- throw new RuntimeException("Font resource contained no fonts.");
- }
- if (families.size() > 1) {
- throw new RuntimeException("Font resource contained more than one family.");
- }
- FontConfig.Family family = families.get(0);
-
- FontFamily fontFamily = new FontFamily();
- List<FontConfig.Font> fonts = family.getFonts();
- for (int i = 0; i < fonts.size(); i++) {
- FontConfig.Font font = fonts.get(i);
- // TODO: Use style and weight info
- if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
- 0 /* resourceCookie */, false /* isAsset */)) {
- return null;
- }
- }
- fontFamily.freeze();
- FontFamily[] familyChain = { fontFamily };
- typeface = createFromFamiliesWithDefault(familyChain);
sDynamicTypefaceCache.put(key, typeface);
- return typeface;
}
+ return typeface;
}
return null;
}
@@ -372,6 +390,18 @@
void onTypefaceRequestFailed(@FontRequestFailReason int reason);
}
+ private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() {
+ @Override
+ public void onTypefaceRetrieved(Typeface typeface) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {
+ // Do nothing.
+ }
+ };
+
/**
* Create a typeface object given a family name, and option style information.
* If null is passed for the name, then the "default" font will be chosen.
diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
index e4f1788a..472b229 100644
--- a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
@@ -427,7 +427,7 @@
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(parser.getPositionDescription()
- + ": <foreground> or <background> tag requires a 'color' or 'drawable'"
+ + ": <foreground> or <background> tag requires a 'drawable'"
+ "attribute or child tag defining a drawable");
}
@@ -451,12 +451,6 @@
layer.mThemeAttrs = a.extractThemeAttrs();
Drawable dr = a.getDrawable(R.styleable.MaskableIconDrawableLayer_drawable);
- if (dr == null) {
- int color = a.getColor(R.styleable.MaskableIconDrawableLayer_color, Color.TRANSPARENT);
- if (color != Color.TRANSPARENT) {
- dr = new ColorDrawable(color);
- }
- }
if (dr != null) {
if (layer.mDrawable != null) {
// It's possible that a drawable was already set, in which case
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index e566b9d..9981668 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -588,7 +588,7 @@
* @hide for reuse by CertInstaller and Settings.
* @see KeyChain#bind
*/
- public final static class KeyChainConnection implements Closeable {
+ public static class KeyChainConnection implements Closeable {
private final Context context;
private final ServiceConnection serviceConnection;
private final IKeyChainService service;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 9701b0e..988e32c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -200,6 +200,11 @@
}
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+ if (mKeySizeBits < 64) {
+ throw new InvalidAlgorithmParameterException(
+ "HMAC key size must be at least 64 bits.");
+ }
+
// JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
// implies SHA-256 digest). Because keymaster HMAC key is authorized only for
// one digest, we don't let algorithm parameter spec override the digest implied
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 415e850..ff90160 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -31,7 +31,7 @@
, mBlend(false)
, mSurfaceTexture(nullptr)
, mTransform(nullptr)
- , mNeedsGLContextAttach(false)
+ , mGLContextAttached(false)
, mUpdateTexImage(false)
, mLayer(nullptr)
, mLayerApi(layerApi)
@@ -47,10 +47,21 @@
}
void DeferredLayerUpdater::destroyLayer() {
- if (mLayer) {
- mLayer->postDecStrong();
- mLayer = nullptr;
+ if (!mLayer) {
+ return;
}
+
+ if (mSurfaceTexture.get() && mLayerApi == Layer::Api::OpenGL && mGLContextAttached) {
+ status_t err = mSurfaceTexture->detachFromContext();
+ mGLContextAttached = false;
+ if (err != 0) {
+ // TODO: Elevate to fatal exception
+ ALOGE("Failed to detach SurfaceTexture from context %d", err);
+ }
+ }
+
+ mLayer->postDecStrong();
+ mLayer = nullptr;
}
void DeferredLayerUpdater::setPaint(const SkPaint* paint) {
@@ -78,14 +89,17 @@
LOG_ALWAYS_FATAL_IF(mLayer->getApi() != Layer::Api::OpenGL,
"apply surfaceTexture with non GL backend %x, GL %x, VK %x",
mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan);
- if (mNeedsGLContextAttach) {
- mNeedsGLContextAttach = false;
+ if (!mGLContextAttached) {
+ mGLContextAttached = true;
+ mUpdateTexImage = true;
mSurfaceTexture->attachToContext(static_cast<GlLayer*>(mLayer)->getTextureId());
}
if (mUpdateTexImage) {
mUpdateTexImage = false;
doUpdateTexImage();
}
+ GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget();
+ static_cast<GlLayer*>(mLayer)->setRenderTarget(renderTarget);
}
if (mTransform) {
mLayer->getTransform().load(*mTransform);
@@ -128,12 +142,8 @@
}
#endif
mSurfaceTexture->getTransformMatrix(transform);
- GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget();
- LOG_ALWAYS_FATAL_IF(renderTarget != GL_TEXTURE_2D && renderTarget != GL_TEXTURE_EXTERNAL_OES,
- "doUpdateTexImage target %x, 2d %x, EXT %x",
- renderTarget, GL_TEXTURE_2D, GL_TEXTURE_EXTERNAL_OES);
- updateLayer(forceFilter, renderTarget, transform);
+ updateLayer(forceFilter, transform);
}
}
@@ -143,42 +153,22 @@
mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan);
static const mat4 identityMatrix;
- updateLayer(false, GL_NONE, identityMatrix.data);
+ updateLayer(false, identityMatrix.data);
VkLayer* vkLayer = static_cast<VkLayer*>(mLayer);
vkLayer->updateTexture();
}
-void DeferredLayerUpdater::updateLayer(bool forceFilter, GLenum renderTarget,
- const float* textureTransform) {
+void DeferredLayerUpdater::updateLayer(bool forceFilter, const float* textureTransform) {
mLayer->setBlend(mBlend);
mLayer->setForceFilter(forceFilter);
mLayer->setSize(mWidth, mHeight);
mLayer->getTexTransform().load(textureTransform);
-
- if (mLayer->getApi() == Layer::Api::OpenGL) {
- GlLayer* glLayer = static_cast<GlLayer*>(mLayer);
- if (renderTarget != glLayer->getRenderTarget()) {
- glLayer->setRenderTarget(renderTarget);
- glLayer->bindTexture();
- glLayer->setFilter(GL_NEAREST, false, true);
- glLayer->setWrap(GL_CLAMP_TO_EDGE, false, true);
- }
- }
}
void DeferredLayerUpdater::detachSurfaceTexture() {
if (mSurfaceTexture.get()) {
- if (mLayerApi == Layer::Api::OpenGL) {
- status_t err = mSurfaceTexture->detachFromContext();
- if (err != 0) {
- // TODO: Elevate to fatal exception
- ALOGE("Failed to detach SurfaceTexture from context %d", err);
- }
- if (mLayer) {
- static_cast<GlLayer*>(mLayer)->clearTexture();
- }
- }
+ destroyLayer();
mSurfaceTexture = nullptr;
}
}
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 064b724..6164e47 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -68,9 +68,8 @@
return false;
}
- ANDROID_API void setSurfaceTexture(const sp<GLConsumer>& texture, bool needsAttach) {
+ ANDROID_API void setSurfaceTexture(const sp<GLConsumer>& texture) {
if (texture.get() != mSurfaceTexture.get()) {
- mNeedsGLContextAttach = needsAttach;
mSurfaceTexture = texture;
GLenum target = texture->getCurrentTextureTarget();
@@ -102,7 +101,7 @@
void detachSurfaceTexture();
- void updateLayer(bool forceFilter, GLenum renderTarget, const float* textureTransform);
+ void updateLayer(bool forceFilter, const float* textureTransform);
void destroyLayer();
@@ -122,7 +121,7 @@
SkBlendMode mMode = SkBlendMode::kSrcOver;
sp<GLConsumer> mSurfaceTexture;
SkMatrix* mTransform;
- bool mNeedsGLContextAttach;
+ bool mGLContextAttached;
bool mUpdateTexImage;
Layer* mLayer;
diff --git a/libs/hwui/GlLayer.cpp b/libs/hwui/GlLayer.cpp
index 8174bcc..070e954 100644
--- a/libs/hwui/GlLayer.cpp
+++ b/libs/hwui/GlLayer.cpp
@@ -43,7 +43,10 @@
}
GlLayer::~GlLayer() {
- if (texture.mId) {
+ // There's a rare possibility that Caches could have been destroyed already
+ // since this method is queued up as a task.
+ // Since this is a reset method, treat this as non-fatal.
+ if (caches.isInitialized() && texture.mId) {
texture.deleteTexture();
}
}
@@ -52,9 +55,15 @@
texture.deleteTexture();
}
-void GlLayer::bindTexture() const {
- if (texture.mId) {
- caches.textureState().bindTexture(texture.target(), texture.mId);
+void GlLayer::setRenderTarget(GLenum renderTarget) {
+ if (renderTarget != getRenderTarget()) {
+ // new render target: bind with new target, and update filter/wrap
+ texture.mTarget = renderTarget;
+ if (texture.mId) {
+ caches.textureState().bindTexture(texture.target(), texture.mId);
+ }
+ texture.setFilter(GL_NEAREST, false, true);
+ texture.setWrap(GL_CLAMP_TO_EDGE, false, true);
}
}
@@ -64,15 +73,5 @@
}
}
-void GlLayer::clearTexture() {
- // There's a rare possibility that Caches could have been destroyed already
- // since this method is queued up as a task.
- // Since this is a reset method, treat this as non-fatal.
- if (caches.isInitialized()) {
- caches.textureState().unbindTexture(texture.mId);
- }
- texture.mId = 0;
-}
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/GlLayer.h b/libs/hwui/GlLayer.h
index 23dfd9d..20aaf4a 100644
--- a/libs/hwui/GlLayer.h
+++ b/libs/hwui/GlLayer.h
@@ -68,33 +68,15 @@
return texture.target();
}
- inline void setRenderTarget(GLenum renderTarget) {
- texture.mTarget = renderTarget;
- }
-
inline bool isRenderable() const {
return texture.target() != GL_NONE;
}
- void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
- texture.setWrap(wrap, bindTexture, force);
- }
+ void setRenderTarget(GLenum renderTarget);
- void setFilter(GLenum filter, bool bindTexture = false, bool force = false) {
- texture.setFilter(filter, bindTexture, force);
- }
-
- void bindTexture() const;
void generateTexture();
/**
- * When the caller frees the texture itself, the caller
- * must call this method to tell this layer that it lost
- * the texture.
- */
- void clearTexture();
-
- /**
* Lost the GL context but the layer is still around, mark it invalid internally
* so the dtor knows not to do any GL work
*/
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index e54bc36..c57b1b3 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -198,9 +198,7 @@
int SkiaCanvas::saveLayer(float left, float top, float right, float bottom,
const SkPaint* paint, SaveFlags::Flags flags) {
const SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
- //always save matrix and clip to match the behaviour of Skia and HWUI pipelines and to ensure
- //android state tracking behavior matches that of the Skia API (partial save is not supported)
- const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags | SaveFlags::MatrixClip));
+ const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));
return mCanvas->saveLayer(rec);
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index de80ee3..f2b0eb3 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -128,6 +128,8 @@
return false;
}
+ // acquire most recent buffer for drawing
+ deferredLayer->updateTexImage();
deferredLayer->apply();
SkCanvas canvas(*bitmap);
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index 8a5d9cc..acd6110 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -120,6 +120,8 @@
bool OpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
ATRACE_CALL();
+ // acquire most recent buffer for drawing
+ layer->updateTexImage();
layer->apply();
return OpenGLReadbackImpl::copyLayerInto(mRenderThread,
static_cast<GlLayer&>(*layer->backingLayer()), bitmap);
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 3e52c39..64ec58d 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -74,7 +74,11 @@
layerUpdater->setTransform(&transform);
// updateLayer so it's ready to draw
- layerUpdater->updateLayer(true, GL_TEXTURE_EXTERNAL_OES, Matrix4::identity().data);
+ layerUpdater->updateLayer(true, Matrix4::identity().data);
+ if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) {
+ static_cast<GlLayer*>(layerUpdater->backingLayer())->setRenderTarget(
+ GL_TEXTURE_EXTERNAL_OES);
+ }
return layerUpdater;
}
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index e03c9e8..a7ebb68 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -48,21 +48,17 @@
SkBitmap bitmap;
SkPaint paint;
hwuiBitmap->getSkBitmapForShaders(&bitmap);
-
- sk_sp<SkShader> repeatShader = SkMakeBitmapShader(bitmap,
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkShader> repeatShader = image->makeShader(
SkShader::TileMode::kRepeat_TileMode,
SkShader::TileMode::kRepeat_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
nullptr);
paint.setShader(std::move(repeatShader));
canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
- sk_sp<SkShader> mirrorShader = SkMakeBitmapShader(bitmap,
+ sk_sp<SkShader> mirrorShader = image->makeShader(
SkShader::TileMode::kMirror_TileMode,
SkShader::TileMode::kMirror_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
nullptr);
paint.setShader(std::move(mirrorShader));
canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 1ef9dba..87d897e 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -44,7 +44,12 @@
// push the deferred updates to the layer
Matrix4 scaledMatrix;
scaledMatrix.loadScale(0.5, 0.5, 0.0);
- layerUpdater->updateLayer(true, GL_TEXTURE_EXTERNAL_OES, scaledMatrix.data);
+ layerUpdater->updateLayer(true, scaledMatrix.data);
+ if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) {
+ GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer());
+ glLayer->setRenderTarget(GL_TEXTURE_EXTERNAL_OES);
+ }
+
// the backing layer should now have all the properties applied.
if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 124f5fa..669f03c 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -748,11 +748,10 @@
SkPaint paint;
SkBitmap skBitmap;
bitmap->getSkBitmap(&skBitmap);
- sk_sp<SkShader> shader = SkMakeBitmapShader(skBitmap,
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkShader> shader = image->makeShader(
SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
nullptr);
paint.setShader(std::move(shader));
canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
@@ -767,11 +766,10 @@
SkPaint paint;
SkBitmap skBitmap;
bitmap->getSkBitmap(&skBitmap);
- sk_sp<SkShader> shader1 = SkMakeBitmapShader(skBitmap,
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkShader> shader1 = image->makeShader(
SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
nullptr);
SkPoint center;
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 03e6b7f..7ae58a6 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -39,12 +39,10 @@
*/
TEST(SkiaBehavior, CreateBitmapShader1x1) {
SkBitmap origBitmap = createSkBitmap(1, 1);
- sk_sp<SkShader> s = SkMakeBitmapShader(
- origBitmap,
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(origBitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkShader> s = image->makeShader(
SkShader::kClamp_TileMode,
SkShader::kRepeat_TileMode,
- nullptr,
- kNever_SkCopyPixelsMode,
nullptr);
SkBitmap bitmap;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fb3f5b3..a4f2a7e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1456,10 +1456,11 @@
}
/**
- * Checks whether A2DP audio routing to the Bluetooth headset is on or off.
+ * Checks whether a Bluetooth A2DP audio peripheral is connected or not.
*
- * @return true if A2DP audio is being routed to/from Bluetooth headset;
+ * @return true if a Bluetooth A2DP peripheral is connected
* false if otherwise
+ * @deprecated Use {@link AudioManager#getDevices(int)} instead to list available audio devices.
*/
public boolean isBluetoothA2dpOn() {
if (AudioSystem.getDeviceConnectionState(DEVICE_OUT_BLUETOOTH_A2DP,"")
@@ -1492,7 +1493,7 @@
*
* @return true if a wired headset is connected.
* false if otherwise
- * @deprecated Use only to check is a headset is connected or not.
+ * @deprecated Use {@link AudioManager#getDevices(int)} instead to list available audio devices.
*/
public boolean isWiredHeadsetOn() {
if (AudioSystem.getDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET,"")
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 9083c16..ddd8a65 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1741,6 +1741,17 @@
return setStereoVolume(gain, gain);
}
+ @Override
+ /* package */ int playerApplyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation) {
+ return native_applyVolumeShaper(configuration, operation);
+ }
+
+ @Override
+ /* package */ @Nullable VolumeShaper.State playerGetVolumeShaperState(int id) {
+ return native_getVolumeShaperState(id);
+ }
/**
* Sets the playback sample rate for this track. This sets the sampling rate at which
@@ -3093,6 +3104,12 @@
private native final void native_disableDeviceCallback();
static private native int native_get_FCC_8();
+ private native int native_applyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation);
+
+ private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+
//---------------------------------------------------------
// Utility methods
//------------------
diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl
index f068a0a..2d60bf9 100644
--- a/media/java/android/media/IPlayer.aidl
+++ b/media/java/android/media/IPlayer.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.media.VolumeShaper;
/**
* @hide
@@ -27,4 +28,6 @@
oneway void setVolume(float vol);
oneway void setPan(float pan);
oneway void setStartDelayMs(int delayMs);
+ oneway void applyVolumeShaper(in VolumeShaper.Configuration configuration,
+ in VolumeShaper.Operation operation);
}
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index d6bf421..228a6de 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -61,8 +61,9 @@
private final static int MAX_REDIRECTS = 20;
public MediaHTTPConnection() {
- if (CookieHandler.getDefault() == null) {
- CookieHandler.setDefault(new CookieManager());
+ CookieManager cookieManager = (CookieManager)CookieHandler.getDefault();
+ if (cookieManager == null) {
+ Log.w(TAG, "MediaHTTPConnection: Unexpected. No CookieManager found.");
}
native_setup();
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 52a68bf..b678630 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -19,25 +19,78 @@
import android.os.IBinder;
import android.util.Log;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.util.List;
+
/** @hide */
public class MediaHTTPService extends IMediaHTTPService.Stub {
private static final String TAG = "MediaHTTPService";
+ private List<HttpCookie> mCookies;
+ private Boolean mCookieStoreInitialized = new Boolean(false);
- public MediaHTTPService() {
+ public MediaHTTPService(List<HttpCookie> cookies) {
+ mCookies = cookies;
+ Log.v(TAG, "MediaHTTPService(" + this + "): Cookies: " + cookies);
}
public IMediaHTTPConnection makeHTTPConnection() {
+
+ synchronized (mCookieStoreInitialized) {
+ // Only need to do it once for all connections
+ if ( !mCookieStoreInitialized ) {
+ CookieManager cookieManager = (CookieManager)CookieHandler.getDefault();
+ if (cookieManager == null) {
+ cookieManager = new CookieManager();
+ CookieHandler.setDefault(cookieManager);
+ Log.v(TAG, "makeHTTPConnection: CookieManager created: " + cookieManager);
+ }
+ else {
+ Log.v(TAG, "makeHTTPConnection: CookieManager(" + cookieManager + ") exists.");
+ }
+
+ // Applying the bootstrapping cookies
+ if ( mCookies != null ) {
+ CookieStore store = cookieManager.getCookieStore();
+ for ( HttpCookie cookie : mCookies ) {
+ try {
+ store.add(null, cookie);
+ } catch ( Exception e ) {
+ Log.v(TAG, "makeHTTPConnection: CookieStore.add" + e);
+ }
+ //for extended debugging when needed
+ //Log.v(TAG, "MediaHTTPConnection adding Cookie[" + cookie.getName() +
+ // "]: " + cookie);
+ }
+ } // mCookies
+
+ mCookieStoreInitialized = true;
+
+ Log.v(TAG, "makeHTTPConnection(" + this + "): cookieManager: " + cookieManager +
+ " Cookies: " + mCookies);
+ } // mCookieStoreInitialized
+ } // synchronized
+
return new MediaHTTPConnection();
}
/* package private */static IBinder createHttpServiceBinderIfNecessary(
String path) {
+ return createHttpServiceBinderIfNecessary(path, null);
+ }
+
+ // when cookies are provided
+ static IBinder createHttpServiceBinderIfNecessary(
+ String path, List<HttpCookie> cookies) {
if (path.startsWith("http://") || path.startsWith("https://")) {
- return (new MediaHTTPService()).asBinder();
+ return (new MediaHTTPService(cookies)).asBinder();
} else if (path.startsWith("widevine://")) {
Log.d(TAG, "Widevine classic is no longer supported");
}
return null;
}
+
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1a1d0f3..5008a5f 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -73,6 +73,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
@@ -80,6 +81,7 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
@@ -638,6 +640,8 @@
private UUID mDrmUUID;
private final Object mDrmLock = new Object();
private DrmInfo mDrmInfo;
+ private MediaDrm mDrmObj;
+ private byte[] mDrmSessionId;
private boolean mDrmInfoResolved;
private boolean mActiveDrmScheme;
private boolean mDrmConfigAllowed;
@@ -998,7 +1002,7 @@
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
- setDataSource(context, uri, null);
+ setDataSource(context, uri, null, null);
}
/**
@@ -1011,11 +1015,13 @@
* changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
* to disallow or allow cross domain redirection.
+ * The headers must not include cookies. Instead, use the cookies param.
+ * @param cookies the cookies to be sent together with the request
* @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
- @Nullable Map<String, String> headers) throws IOException, IllegalArgumentException,
- SecurityException, IllegalStateException {
+ @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// The context and URI usually belong to the calling user. Get a resolver for that user
// and strip out the userId from the URI if present.
final ContentResolver resolver = context.getContentResolver();
@@ -1036,18 +1042,36 @@
} else if (attemptDataSource(resolver, actualUri)) {
return;
} else {
- setDataSource(uri.toString(), headers);
+ setDataSource(uri.toString(), headers, cookies);
}
} else {
// Try requested Uri locally first, or fallback to media server
if (attemptDataSource(resolver, uri)) {
return;
} else {
- setDataSource(uri.toString(), headers);
+ setDataSource(uri.toString(), headers, cookies);
}
}
}
+ /**
+ * Sets the data source as a content Uri.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param headers the headers to be sent together with the request for the data
+ * Note that the cross domain redirection is allowed by default, but that can be
+ * changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
+ * to disallow or allow cross domain redirection.
+ * @throws IllegalStateException if it is called in an invalid state
+ */
+ public void setDataSource(@NonNull Context context, @NonNull Uri uri,
+ @Nullable Map<String, String> headers)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ setDataSource(context, uri, headers, null);
+ }
+
private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
setDataSource(afd);
@@ -1085,6 +1109,11 @@
* @hide pending API council
*/
public void setDataSource(String path, Map<String, String> headers)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ setDataSource(path, headers, null);
+ }
+
+ private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
String[] keys = null;
@@ -1101,10 +1130,11 @@
++i;
}
}
- setDataSource(path, keys, values);
+ setDataSource(path, keys, values, cookies);
}
- private void setDataSource(String path, String[] keys, String[] values)
+ private void setDataSource(String path, String[] keys, String[] values,
+ List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
@@ -1113,7 +1143,7 @@
} else if (scheme != null) {
// handle non-file sources
nativeSetDataSource(
- MediaHTTPService.createHttpServiceBinderIfNecessary(path),
+ MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
@@ -1331,6 +1361,24 @@
stop();
}
+ @Override
+ /* package */ int playerApplyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation) {
+ return native_applyVolumeShaper(configuration, operation);
+ }
+
+ @Override
+ /* package */ @Nullable VolumeShaper.State playerGetVolumeShaperState(int id) {
+ return native_getVolumeShaperState(id);
+ }
+
+ private native int native_applyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation);
+
+ private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+
/**
* Set the low-level power management behavior for this MediaPlayer. This
* can be used when the MediaPlayer is not playing through a SurfaceHolder
@@ -1914,6 +1962,7 @@
mOnSubtitleDataListener = null;
// Modular DRM clean up
+ mOnDrmConfigListener = null;
mOnDrmInfoHandlerDelegate = null;
mOnDrmPreparedHandlerDelegate = null;
resetDrmState();
@@ -3125,7 +3174,7 @@
onDrmInfoHandlerDelegate.notifyClient(drmInfo);
}
} else {
- Log.w(TAG, "MEDIA_DRM_INFO msg.obj NONE; UNEXPECTED" + msg.obj);
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
}
return;
@@ -3800,17 +3849,34 @@
* and setDrmPropertyString.
*
*/
- public static abstract class OnDrmConfigCallback
+ public interface OnDrmConfigListener
{
/**
* Called to give the app the opportunity to configure DRM before the session is created
*
* @param mp the {@code MediaPlayer} associated with this callback
*/
- public void onDrmConfig(MediaPlayer mp) {}
+ public void onDrmConfig(MediaPlayer mp);
}
/**
+ * Register a callback to be invoked for configuration of the DRM object before
+ * the session is created.
+ * The callback will be invoked synchronously half-way into the execution
+ * of {@link #prepareDrm(UUID uuid)}.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmConfigListener(OnDrmConfigListener listener)
+ {
+ synchronized (mDrmLock) {
+ mOnDrmConfigListener = listener;
+ } // synchronized
+ }
+
+ private OnDrmConfigListener mOnDrmConfigListener;
+
+ /**
* Interface definition of a callback to be invoked when the
* DRM info becomes available
*/
@@ -4007,13 +4073,11 @@
return drmInfo;
}
- private native void _prepareDrm(@NonNull byte[] uuid, int mode)
- throws UnsupportedSchemeException, ResourceBusyException, NotProvisionedException;
/**
* Prepares the DRM for the current source
* <p>
- * If {@code OnDrmConfigCallback} is registered, it will be called half-way into
+ * If {@code OnDrmConfigListener} is registered, it will be called half-way into
* preparation to allow configuration of the DRM properties before opening the
* DRM session. Note that the callback is called synchronously in the thread that called
* {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
@@ -4041,10 +4105,12 @@
* @throws ResourceBusyException if required DRM resources are in use
* @throws ProvisioningErrorException if provisioning is required but an attempt failed
*/
- public void prepareDrm(@NonNull UUID uuid, OnDrmConfigCallback configCallback)
+ public void prepareDrm(@NonNull UUID uuid)
throws UnsupportedSchemeException,
ResourceBusyException, ProvisioningErrorException
{
+ Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigListener: " + mOnDrmConfigListener);
+
boolean allDoneWithoutProvisioning = false;
// get a snapshot as we'll use them outside the lock
OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null;
@@ -4053,58 +4119,46 @@
// only allowing if tied to a protected source; might releax for releasing offline keys
if (mDrmInfo == null) {
- final String msg = String.format("prepareDrm(%s): Wrong usage: " +
- "The player must be prepared and DRM " +
- "info be retrieved before this call.", uuid);
+ final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
+ "DRM info be retrieved before this call.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
if (mActiveDrmScheme) {
- final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
- "an active DRM scheme with %s.", uuid, mDrmUUID);
+ final String msg = "prepareDrm(): Wrong usage: There is already " +
+ "an active DRM scheme with " + mDrmUUID;
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
if (mPrepareDrmInProgress) {
- final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
- "a pending prepareDrm call.", uuid);
+ final String msg = "prepareDrm(): Wrong usage: There is already " +
+ "a pending prepareDrm call.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
if (mDrmProvisioningInProgress) {
- final String msg = String.format("prepareDrm(%s): Unexpectd: Provisioning is " +
- "already in progress.", uuid);
+ final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
+ // shouldn't need this; just for safeguard
+ cleanDrmObj();
+
mPrepareDrmInProgress = true;
// local copy while the lock is held
onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate;
- if (configCallback != null) {
- try {
- boolean allowOpenSession = false; // just pre-openSession
- _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
- } catch (IllegalStateException e) {
- final String msg = String.format("prepareDrm(): Wrong usage: The player must " +
- "be in prepared state to call prepareDrm().");
- Log.e(TAG, msg);
- throw new IllegalStateException(msg);
- } catch (NotProvisionedException e) { // the pre-config step won't raise this
- final String msg = String.format("prepareDrm: Unexpected " +
- "NotProvisionedException here.");
- Log.e(TAG, msg);
- throw new ProvisioningErrorException(msg);
- } catch (Exception e) {
- Log.w(TAG, String.format("prepareDrm: Exception %s", e));
- throw e;
- } finally {
- mPrepareDrmInProgress = false;
- }
+ try {
+ // only creating the DRM object to allow pre-openSession configuration
+ prepareDrm_createDrmStep(uuid);
+ } catch (Exception e) {
+ Log.w(TAG, "prepareDrm(): Exception ", e);
+ mPrepareDrmInProgress = false;
+ throw e;
}
mDrmConfigAllowed = true;
@@ -4112,51 +4166,55 @@
// call the callback outside the lock
- if (configCallback != null) {
- configCallback.onDrmConfig(this);
+ if (mOnDrmConfigListener != null) {
+ mOnDrmConfigListener.onDrmConfig(this);
}
synchronized (mDrmLock) {
mDrmConfigAllowed = false;
+ boolean earlyExit = false;
try {
- boolean allowOpenSession = true; // all in
- _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+ prepareDrm_openSessionStep(uuid);
mDrmUUID = uuid;
mActiveDrmScheme = true;
- mPrepareDrmInProgress = false;
-
allDoneWithoutProvisioning = true;
} catch (IllegalStateException e) {
- final String msg = String.format("prepareDrm(%s): Wrong usage: The player must be" +
- " in prepared state to call prepareDrm().", uuid);
+ final String msg = "prepareDrm(): Wrong usage: The player must be " +
+ "in the prepared state to call prepareDrm().";
Log.e(TAG, msg);
+ earlyExit = true;
throw new IllegalStateException(msg);
} catch (NotProvisionedException e) {
- Log.w(TAG, String.format("prepareDrm: NotProvisionedException"));
+ Log.w(TAG, "prepareDrm: NotProvisionedException");
- // handle provisioning internally
+ // handle provisioning internally; it'll reset mPrepareDrmInProgress
boolean result = HandleProvisioninig(uuid);
// if blocking mode, we're already done;
// if non-blocking mode, we attempted to launch background provisioning
if (result == false) {
- final String msg =
- String.format("prepareDrm: Provisioning was required but failed.");
+ final String msg = "prepareDrm: Provisioning was required but failed.";
Log.e(TAG, msg);
+ earlyExit = true;
throw new ProvisioningErrorException(msg);
}
-
// nothing else to do;
// if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
} catch (Exception e) {
- Log.w(TAG, String.format("prepareDrm: Exception %s", e));
+ Log.e(TAG, "prepareDrm: Exception " + e);
+ earlyExit = true;
throw e;
} finally {
- mPrepareDrmInProgress = false;
- }
+ if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception
+ mPrepareDrmInProgress = false;
+ }
+ if (earlyExit) { // cleaning up object if didn't succeed
+ cleanDrmObj();
+ }
+ } // finally
} // synchronized
@@ -4179,25 +4237,33 @@
public void releaseDrm()
throws NoDrmSchemeException
{
+ Log.v(TAG, "releaseDrm:");
+
synchronized (mDrmLock) {
if (!mActiveDrmScheme) {
- Log.e(TAG, String.format("releaseDrm(%s): No active DRM scheme to release."));
+ Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release.");
- } else {
+ }
+
+ try {
+ // we don't have the player's state in this layer. The below call raises
+ // exception if we're in a non-stopped/idle state.
+
+ // for cleaning native/mediaserver crypto object
_releaseDrm();
+ // for cleaning client-side MediaDrm object; only called if above has succeeded
+ cleanDrmObj();
+
mActiveDrmScheme = false;
+ } catch (Exception e) {
+ Log.w(TAG, "releaseDrm: Exception ", e);
+ throw e;
}
} // synchronized
}
- @NonNull
- private native MediaDrm.KeyRequest _getKeyRequest(@NonNull byte[] scope,
- @Nullable String mimeType, @MediaDrm.KeyType int keyType,
- @Nullable Map<String, String> optionalParameters)
- throws NotProvisionedException;
-
/**
* A key request/response exchange occurs between the app and a license server
* to obtain or release keys used to decrypt encrypted content.
@@ -4238,20 +4304,42 @@
@MediaDrm.KeyType int keyType, @Nullable Map<String, String> optionalParameters)
throws NoDrmSchemeException
{
+ Log.v(TAG, "getKeyRequest: " +
+ " scope: " + scope + " mimeType: " + mimeType +
+ " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+
synchronized (mDrmLock) {
if (!mActiveDrmScheme) {
- Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ Log.e(TAG, "getKeyRequest NoDrmSchemeException");
throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
}
try {
- return _getKeyRequest(scope, mimeType, keyType, optionalParameters);
+ byte[] scopeOut = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ scope; // keySetId for KEY_TYPE_RELEASE
+
+ byte[] initData = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+ scope : // initData for KEY_TYPE_STREAMING/OFFLINE
+ null; // not used for KEY_TYPE_RELEASE
+
+ HashMap<String, String> hmapOptionalParameters =
+ (optionalParameters != null) ?
+ new HashMap<String, String>(optionalParameters) :
+ null;
+
+ MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scopeOut, initData, mimeType,
+ keyType, hmapOptionalParameters);
+ Log.v(TAG, "getKeyRequest: --> request: " + request);
+
+ return request;
+
} catch (NotProvisionedException e) {
- Log.w(TAG, String.format("getKeyRequest NotProvisionedException: " +
- "Unexpected. Shouldn't have reached here."));
+ Log.w(TAG, "getKeyRequest NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
throw new IllegalStateException("getKeyRequest: Unexpected provisioning error.");
} catch (Exception e) {
- Log.w(TAG, String.format("getKeyRequest Exception %s", e));
+ Log.w(TAG, "getKeyRequest Exception " + e);
throw e;
}
@@ -4259,10 +4347,6 @@
}
- @Nullable
- private native byte[] _provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
- throws DeniedByServerException;
-
/**
* A key response is received from the license server by the app, then it is
* provided to the DRM engine plugin using provideKeyResponse. When the
@@ -4285,25 +4369,41 @@
public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
throws NoDrmSchemeException, DeniedByServerException
{
+ Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response);
+
synchronized (mDrmLock) {
if (!mActiveDrmScheme) {
- Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ Log.e(TAG, "getKeyRequest NoDrmSchemeException");
throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
}
try {
- return _provideKeyResponse(keySetId, response);
+ byte[] scope = (keySetId == null) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+ Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response +
+ " --> " + keySetResult);
+
+
+ return keySetResult;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "provideKeyResponse NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("provideKeyResponse: " +
+ "Unexpected provisioning error.");
} catch (Exception e) {
- Log.w(TAG, String.format("provideKeyResponse Exception %s", e));
+ Log.w(TAG, "provideKeyResponse Exception " + e);
throw e;
}
} // synchronized
}
- private native void _restoreKeys(@NonNull byte[] keySetId);
-
/**
* Restore persisted offline keys into a new session. keySetId identifies the
* keys to load, obtained from a prior call to {@link #provideKeyResponse}.
@@ -4313,17 +4413,19 @@
public void restoreKeys(@NonNull byte[] keySetId)
throws NoDrmSchemeException
{
+ Log.v(TAG, "restoreKeys: keySetId: " + keySetId);
+
synchronized (mDrmLock) {
if (!mActiveDrmScheme) {
- Log.w(TAG, String.format("restoreKeys NoDrmSchemeException"));
+ Log.w(TAG, "restoreKeys NoDrmSchemeException");
throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first.");
}
try {
- _restoreKeys(keySetId);
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
} catch (Exception e) {
- Log.w(TAG, String.format("restoreKeys Exception %s", e));
+ Log.w(TAG, "restoreKeys Exception " + e);
throw e;
}
@@ -4331,9 +4433,6 @@
}
- @NonNull
- private native String _getDrmPropertyString(@NonNull String propertyName);
-
/**
* Read a DRM engine plugin String property value, given the property name string.
* <p>
@@ -4347,26 +4446,29 @@
public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
throws NoDrmSchemeException
{
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
+
String value;
synchronized (mDrmLock) {
if (!mActiveDrmScheme && !mDrmConfigAllowed) {
- Log.w(TAG, String.format("getDrmPropertyString NoDrmSchemeException"));
+ Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first.");
}
try {
- value = _getDrmPropertyString(propertyName);
+ value = mDrmObj.getPropertyString(propertyName);
} catch (Exception e) {
- Log.w(TAG, String.format("getDrmPropertyString Exception %s", e));
+ Log.w(TAG, "getDrmPropertyString Exception " + e);
throw e;
}
} // synchronized
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
+
return value;
}
- private native void _setDrmPropertyString(@NonNull String propertyName, @NonNull String value);
/**
* Set a DRM engine plugin String property value.
@@ -4382,17 +4484,19 @@
@NonNull String value)
throws NoDrmSchemeException
{
+ Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
+
synchronized (mDrmLock) {
if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
- Log.w(TAG, String.format("setDrmPropertyString NoDrmSchemeException"));
+ Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first.");
}
try {
- _setDrmPropertyString(propertyName, value);
+ mDrmObj.setPropertyString(propertyName, value);
} catch ( Exception e ) {
- Log.w(TAG, String.format("setDrmPropertyString Exception %s", e));
+ Log.w(TAG, "setDrmPropertyString Exception " + e);
throw e;
}
} // synchronized
@@ -4559,8 +4663,47 @@
}
}
+
+ private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
+
// Modular DRM helpers
+ private void prepareDrm_createDrmStep(@NonNull UUID uuid)
+ throws UnsupportedSchemeException {
+ Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+ try {
+ mDrmObj = new MediaDrm(uuid);
+ Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+ } catch (Exception e) { // UnsupportedSchemeException
+ Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+ throw e;
+ }
+ }
+
+ private void prepareDrm_openSessionStep(@NonNull UUID uuid)
+ throws NotProvisionedException, ResourceBusyException {
+ Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+ // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
+ // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+ // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse
+ try {
+ mDrmSessionId = mDrmObj.openSession();
+ Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+ // Sending it down to native/mediaserver to create the crypto object
+ // This call could simply fail due to bad player state, e.g., after start().
+ _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+ Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded");
+
+ } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+ Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+ throw e;
+ }
+
+ }
+
private class ProvisioningThread extends Thread
{
public static final int TIMEOUT_MS = 60000;
@@ -4587,7 +4730,7 @@
urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
this.uuid = uuid;
- Log.v(TAG, String.format("HandleProvisioninig: Thread is initialised url: %s", urlStr));
+ Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
return this;
}
@@ -4607,30 +4750,27 @@
connection.connect();
response = Streams.readFully(connection.getInputStream());
- Log.v(TAG, String.format("HandleProvisioninig: Thread run response %d %s",
- response.length, response));
+ Log.v(TAG, "HandleProvisioninig: Thread run: response " +
+ response.length + " " + response);
} catch (Exception e) {
- Log.w(TAG, String.format("HandleProvisioninig: Thread run connect %s url: %s",
- e, url));
+ Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
} finally {
connection.disconnect();
}
} catch (Exception e) {
- Log.w(TAG, String.format("HandleProvisioninig: Thread run openConnection %s", e));
+ Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
}
if (response != null) {
try {
- MediaDrm drm = new MediaDrm(uuid);
- drm.provideProvisionResponse(response);
- drm.release();
- Log.v(TAG, String.format("HandleProvisioninig: Thread run " +
- "newDrm+provideProvisionResponse SUCCEEDED!"));
+ mDrmObj.provideProvisionResponse(response);
+ Log.v(TAG, "HandleProvisioninig: Thread run: " +
+ "provideProvisionResponse SUCCEEDED!");
provisioningSucceeded = true;
} catch (Exception e) {
- Log.w(TAG, String.format("HandleProvisioninig: Thread run " +
- "newDrm+provideProvisionResponse %s", e));
+ Log.w(TAG, "HandleProvisioninig: Thread run: " +
+ "provideProvisionResponse " + e);
}
}
@@ -4644,7 +4784,10 @@
}
mediaPlayer.mDrmProvisioningInProgress = false;
mediaPlayer.mPrepareDrmInProgress = false;
- }
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
+ }
+ } // synchronized
// calling the callback outside the lock
onDrmPreparedHandlerDelegate.notifyClient(succeeded);
@@ -4656,6 +4799,9 @@
}
mediaPlayer.mDrmProvisioningInProgress = false;
mediaPlayer.mPrepareDrmInProgress = false;
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through
+ }
}
finished = true;
@@ -4668,24 +4814,18 @@
// the lock is already held by the caller
if (mDrmProvisioningInProgress) {
- Log.e(TAG, String.format("HandleProvisioninig: Unexpected mDrmProvisioningInProgress"));
+ Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
return false;
}
- MediaDrm.ProvisionRequest provReq = null;
- try {
- MediaDrm drm = new MediaDrm(uuid);
- provReq = drm.getProvisionRequest();
- drm.release();
- } catch (Exception e) {
- Log.e(TAG, String.format("HandleProvisioninig: getProvisionRequest failed with %s", e));
+ MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+ if (provReq == null) {
+ Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
return false;
}
- Log.v(TAG, String.format("HandleProvisioninig provReq: data %s url %s",
- (provReq != null) ? provReq.getData() : "-",
- (provReq != null) ? provReq.getDefaultUrl() : "://")
- );
+ Log.v(TAG, "HandleProvisioninig provReq " +
+ " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
// networking in a background thread
mDrmProvisioningInProgress = true;
@@ -4703,7 +4843,7 @@
try {
mDrmProvisioningThread.join();
} catch (Exception e) {
- Log.w(TAG, String.format("HandleProvisioninig: Thread.join Exception %s", e));
+ Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e);
}
result = mDrmProvisioningThread.succeeded();
// no longer need the thread
@@ -4715,19 +4855,21 @@
private boolean resumePrepareDrm(UUID uuid)
{
+ Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
// mDrmLock is guaranteed to be held
boolean success = false;
try {
- boolean allowOpenSession = true; // resuming
- _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+ // resuming
+ prepareDrm_openSessionStep(uuid);
mDrmUUID = uuid;
mActiveDrmScheme = true;
success = true;
} catch (Exception e) {
- Log.w(TAG, String.format("HandleProvisioninig: " +
- "Thread run _prepareDrm resume failed with %s", e));
+ Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e);
+ // mDrmObj clean up is done by the caller
}
return success;
@@ -4736,6 +4878,12 @@
private void resetDrmState()
{
synchronized (mDrmLock) {
+ Log.v(TAG, "resetDrmState: " +
+ " mDrmInfo=" + mDrmInfo +
+ " mDrmProvisioningThread=" + mDrmProvisioningThread +
+ " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
+ " mActiveDrmScheme=" + mActiveDrmScheme);
+
mDrmInfoResolved = false;
mDrmInfo = null;
@@ -4745,15 +4893,33 @@
mDrmProvisioningThread.join();
}
catch (InterruptedException e) {
- Log.w(TAG, String.format("resetDrmState: ProvThread.join Exception %s", e));
+ Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
}
mDrmProvisioningThread = null;
}
mPrepareDrmInProgress = false;
+ mActiveDrmScheme = false;
+
+ cleanDrmObj();
} // synchronized
}
+ private void cleanDrmObj()
+ {
+ // the caller holds mDrmLock
+ Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+ if (mDrmSessionId != null) {
+ mDrmObj.closeSession(mDrmSessionId);
+ mDrmSessionId = null;
+ }
+ if (mDrmObj != null) {
+ mDrmObj.release();
+ mDrmObj = null;
+ }
+ }
+
private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index d2b052a..b397b45 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -17,9 +17,11 @@
package android.media;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.Context;
+import android.media.VolumeShaper;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -357,6 +359,42 @@
* @param rightVolume the right volume to use if muting is false
*/
abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume);
+
+ /**
+ * Abstract method to apply a {@link VolumeShaper.Configuration}
+ * and a {@link VolumeShaper.Operation} to the Player.
+ * This should be overridden by the Player to call into the native
+ * VolumeShaper implementation. Multiple {@code VolumeShapers} may be
+ * concurrently active for a given Player, each accessible by the
+ * {@code VolumeShaper} id.
+ *
+ * The {@code VolumeShaper} implementation caches the id returned
+ * when applying a fully specified configuration
+ * from {VolumeShaper.Configuration.Builder} to track later
+ * operation changes requested on it.
+ *
+ * @param configuration a {@code VolumeShaper.Configuration} object
+ * created by {@link VolumeShaper.Configuration.Builder} or
+ * an created from a {@code VolumeShaper} id
+ * by the {@link VolumeShaper.Configuration} constructor.
+ * @param operation a {@code VolumeShaper.Operation}.
+ * @return a negative error status or a
+ * non-negative {@code VolumeShaper} id on success.
+ */
+ /* package */ abstract int playerApplyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation);
+
+ /**
+ * Abstract method to get the current VolumeShaper state.
+ * @param id the {@code VolumeShaper} id returned from
+ * sending a fully specified {@code VolumeShaper.Configuration}
+ * through {@link #playerApplyVolumeShaper}
+ * @return a {@code VolumeShaper.State} object or null if
+ * there is no {@code VolumeShaper} for the id.
+ */
+ /* package */ abstract @Nullable VolumeShaper.State playerGetVolumeShaperState(int id);
+
abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
abstract void playerStart();
abstract void playerPause();
@@ -396,6 +434,13 @@
public void setStartDelayMs(int delayMs) {
baseSetStartDelayMs(delayMs);
}
+
+ @Override
+ public void applyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation) {
+ /* void */ playerApplyVolumeShaper(configuration, operation);
+ }
};
//=====================================================================
diff --git a/media/java/android/media/PlayerProxy.java b/media/java/android/media/PlayerProxy.java
index 1a2c668..5f3997a 100644
--- a/media/java/android/media/PlayerProxy.java
+++ b/media/java/android/media/PlayerProxy.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.media.VolumeShaper;
import android.os.RemoteException;
import android.util.Log;
@@ -132,4 +133,21 @@
}
}
+ /**
+ * @hide
+ * @param configuration
+ * @param operation
+ * @return volume shaper id or error
+ */
+ public void applyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation) {
+ try {
+ mConf.getIPlayer().applyVolumeShaper(configuration, operation);
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for applyVolumeShaper operation,"
+ + " player already released?", e);
+ }
+ }
}
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 4cc1f8e..dbbbfc6 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -20,6 +20,8 @@
import java.io.FileDescriptor;
import java.lang.ref.WeakReference;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.content.Context;
@@ -388,6 +390,17 @@
_setVolume(streamID, leftVolume, rightVolume);
}
+ @Override
+ /* package */ int playerApplyVolumeShaper(
+ @NonNull VolumeShaper.Configuration configuration,
+ @Nullable VolumeShaper.Operation operation) {
+ return -1;
+ }
+
+ @Override
+ /* package */ @Nullable VolumeShaper.State playerGetVolumeShaperState(int id) {
+ return null;
+ }
@Override
void playerSetVolume(boolean muting, float leftVolume, float rightVolume) {
diff --git a/core/java/android/view/autofill/FillResponse.aidl b/media/java/android/media/VolumeShaper.aidl
similarity index 68%
copy from core/java/android/view/autofill/FillResponse.aidl
copy to media/java/android/media/VolumeShaper.aidl
index b018f15..ecf6a8f 100644
--- a/core/java/android/view/autofill/FillResponse.aidl
+++ b/media/java/android/media/VolumeShaper.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * Copyright 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
+ * 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,
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.view.autofill;
+package android.media;
-parcelable FillResponse;
\ No newline at end of file
+parcelable VolumeShaper.Configuration;
+parcelable VolumeShaper.Operation;
+parcelable VolumeShaper.State;
\ No newline at end of file
diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java
new file mode 100644
index 0000000..77af359
--- /dev/null
+++ b/media/java/android/media/VolumeShaper.java
@@ -0,0 +1,1275 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+
+/**
+ * TODO: remove @hide
+ * The {@code VolumeShaper} class is used to automatically control audio volume during media
+ * playback, allowing for simple implementation of transition effects and ducking.
+ *
+ * The {@link VolumeShaper} appears as an additional scaling on the audio output,
+ * and can be used independently of track or stream volume controls.
+ */
+public final class VolumeShaper {
+ /* member variables */
+ private int mId;
+ private final WeakReference<PlayerBase> mPlayerBase;
+ private final WeakReference<PlayerProxy> mPlayerProxy;
+
+ /**
+ * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and an
+ * {@link AudioTrack}.
+ *
+ * @param configuration
+ * @param audioTrack
+ */
+ public VolumeShaper(@NonNull Configuration configuration, @NonNull AudioTrack audioTrack) {
+ this(configuration, (PlayerBase)audioTrack);
+ }
+
+ /**
+ * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a
+ * {@link MediaPlayer}.
+ *
+ * @param configuration
+ * @param mediaPlayer
+ */
+ public VolumeShaper(@NonNull Configuration configuration, @NonNull MediaPlayer mediaPlayer) {
+ this(configuration, (PlayerBase)mediaPlayer);
+ }
+
+ /* package */ VolumeShaper(
+ @NonNull Configuration configuration, @NonNull PlayerBase playerBase) {
+ mPlayerBase = new WeakReference<PlayerBase>(playerBase);
+ mPlayerProxy = null;
+ mId = applyPlayer(configuration, new Operation.Builder().defer().build());
+ }
+
+ /**
+ * @hide
+ * TODO SystemApi
+ * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a
+ * {@code PlayerProxy} object. The PlayerProxy object requires that the configuration
+ * be set with a system VolumeShaper id (this is a reserved value).
+ *
+ * @param configuration
+ * @param playerProxy
+ */
+ public VolumeShaper(
+ @NonNull Configuration configuration, @NonNull PlayerProxy playerProxy) {
+ if (configuration.getId() < 0) {
+ throw new IllegalArgumentException("playerProxy configuration id must be specified");
+ }
+ mPlayerProxy = new WeakReference<PlayerProxy>(playerProxy);
+ mPlayerBase = null;
+ mId = applyPlayer(configuration, new Operation.Builder().defer().build());
+ }
+
+ /* package */ int getId() {
+ return mId;
+ }
+
+ /**
+ * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}.
+ * @param operation
+ */
+ public void apply(@NonNull Operation operation) {
+ /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation);
+ }
+
+ /**
+ * Replaces the current {@code VolumeShaper}
+ * configuration with a new configuration.
+ *
+ * This can be used to dynamically change the {@code VolumeShaper}
+ * configuration by joining several
+ * {@code VolumeShaper} configurations together.
+ * This is useful if the user changes the volume while the
+ * {@code VolumeShaper} is in effect.
+ *
+ * @param configuration
+ * @param operation
+ * @param join
+ */
+ public void replace(
+ @NonNull Configuration configuration, @NonNull Operation operation, boolean join) {
+ mId = applyPlayer(
+ configuration,
+ new Operation.Builder(operation).replace(mId, join).build());
+ }
+
+ /**
+ * Returns the current volume scale attributable to the {@code VolumeShaper}.
+ *
+ * @return the volume, linearly represented as a value between 0.f and 1.f.
+ */
+ public float getVolume() {
+ return getStatePlayer(mId).getVolume();
+ }
+
+ /**
+ * Releases the {@code VolumeShaper}. Any volume scale due to the
+ * {@code VolumeShaper} is removed.
+ */
+ public void release() {
+ try {
+ /* void */ applyPlayer(
+ new VolumeShaper.Configuration(mId),
+ new Operation.Builder().terminate().build());
+ } catch (IllegalStateException ise) {
+ ; // ok
+ }
+ if (mPlayerBase != null) {
+ mPlayerBase.clear();
+ }
+ if (mPlayerProxy != null) {
+ mPlayerProxy.clear();
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ release(); // ensure we remove the native volume shaper
+ }
+
+ /**
+ * Internal call to apply the configuration and operation to the Player.
+ * Returns a valid shaper id or throws the appropriate exception.
+ * @param configuration
+ * @param operation
+ * @return id a non-negative shaper id.
+ */
+ private int applyPlayer(
+ @NonNull VolumeShaper.Configuration configuration,
+ @NonNull VolumeShaper.Operation operation) {
+ final int id;
+ if (mPlayerProxy != null) {
+ // The PlayerProxy accepts only one way transactions so
+ // the Configuration must have an id set to one of the system
+ // ids (a positive value less than 16).
+ PlayerProxy player = mPlayerProxy.get();
+ if (player == null) {
+ throw new IllegalStateException("player deallocated");
+ }
+ id = configuration.getId();
+ if (id < 0) {
+ throw new IllegalArgumentException("proxy requires configuration with id");
+ }
+ player.applyVolumeShaper(configuration, operation);
+ } else if (mPlayerBase != null) {
+ PlayerBase player = mPlayerBase.get();
+ if (player == null) {
+ throw new IllegalStateException("player deallocated");
+ }
+ id = player.playerApplyVolumeShaper(configuration, operation);
+ } else {
+ throw new IllegalStateException("uninitialized shaper");
+ }
+ if (id < 0) {
+ // TODO - get INVALID_OPERATION from platform.
+ final int VOLUME_SHAPER_INVALID_OPERATION = -38; // must match with platform
+ // Due to RPC handling, we translate integer codes to exceptions right before
+ // delivering to the user.
+ if (id == VOLUME_SHAPER_INVALID_OPERATION) {
+ throw new IllegalStateException("player or volume shaper deallocated");
+ } else {
+ throw new IllegalArgumentException("invalid configuration or operation: " + id);
+ }
+ }
+ return id;
+ }
+
+ /**
+ * Internal call to retrieve the current VolumeShaper state.
+ * @param id
+ * @return the current {@vode VolumeShaper.State}
+ */
+ private @NonNull VolumeShaper.State getStatePlayer(int id) {
+ final VolumeShaper.State state;
+ if (mPlayerProxy != null) {
+ PlayerProxy player = mPlayerProxy.get();
+ if (player == null) {
+ throw new IllegalStateException("player deallocated");
+ }
+ throw new IllegalStateException("getStatePlayer not permitted through proxy");
+ } else if (mPlayerBase != null) {
+ PlayerBase player = mPlayerBase.get();
+ if (player == null) {
+ throw new IllegalStateException("player deallocated");
+ }
+ state = player.playerGetVolumeShaperState(id);
+ } else {
+ throw new IllegalStateException("uninitialized shaper");
+ }
+ if (state == null) {
+ throw new IllegalStateException("shaper cannot be found");
+ }
+ return state;
+ }
+
+ /**
+ * The {@code VolumeShaper.Configuration} class contains curve shape
+ * and parameter information for constructing a {@code VolumeShaper}.
+ * This curve shape and parameter information is specified
+ * on {@code VolumeShaper} creation
+ * and may be replaced through {@link VolumeShaper#replace}.
+ */
+ public static final class Configuration implements Parcelable {
+ private static final int MAXIMUM_CURVE_POINTS = 16;
+
+ /**
+ * Returns the maximum number of curve points allowed for
+ * {@link VolumeShaper.Builder#setCurve(float[], float[])}.
+ */
+ public static int getMaximumCurvePoints() {
+ return MAXIMUM_CURVE_POINTS;
+ }
+
+ // These values must match the native VolumeShaper::Configuration::Type
+ /** @hide */
+ @IntDef({
+ TYPE_ID,
+ TYPE_SCALE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Specifies a {@link VolumeShaper} handle created by {@link #VolumeShaper(int)}
+ * from an id returned by {@code setVolumeShaper()}.
+ * The type, curve, etc. may not be queried from
+ * a {@code VolumeShaper} object of this type;
+ * the handle is used to identify and change the operation of
+ * an existing {@code VolumeShaper} sent to the player.
+ */
+ /* package */ static final int TYPE_ID = 0;
+
+ /**
+ * Specifies a {@link VolumeShaper} to be used
+ * as an additional scale to the current volume.
+ * This is created by the {@link VolumeShaper.Builder}.
+ */
+ /* package */ static final int TYPE_SCALE = 1;
+
+ // These values must match the native InterpolatorType enumeration.
+ /** @hide */
+ @IntDef({
+ INTERPOLATOR_TYPE_STEP,
+ INTERPOLATOR_TYPE_LINEAR,
+ INTERPOLATOR_TYPE_CUBIC,
+ INTERPOLATOR_TYPE_CUBIC_MONOTONIC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InterpolatorType {}
+
+ /**
+ * Stepwise volume curve.
+ */
+ public static final int INTERPOLATOR_TYPE_STEP = 0;
+
+ /**
+ * Linear interpolated volume curve.
+ */
+ public static final int INTERPOLATOR_TYPE_LINEAR = 1;
+
+ /**
+ * Cubic interpolated volume curve.
+ * This is default if unspecified.
+ */
+ public static final int INTERPOLATOR_TYPE_CUBIC = 2;
+
+ /**
+ * Cubic interpolated volume curve
+ * with local monotonicity preservation.
+ * So long as the control points are locally monotonic,
+ * the curve interpolation will also be locally monotonic.
+ * This is useful for cubic spline interpolated
+ * volume ramps and ducks.
+ */
+ public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3;
+
+ // These values must match the native VolumeShaper::Configuration::InterpolatorType
+ /** @hide */
+ @IntDef({
+ OPTION_FLAG_VOLUME_IN_DBFS,
+ OPTION_FLAG_CLOCK_TIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OptionFlag {}
+
+ /**
+ * Use a dB full scale volume range for the volume curve.
+ *<p>
+ * The volume scale is typically from 0.f to 1.f on a linear scale;
+ * this option changes to -inf to 0.f on a db full scale,
+ * where 0.f is equivalent to a scale of 1.f.
+ */
+ public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0);
+
+ /**
+ * Use clock time instead of media time.
+ *<p>
+ * The default implementation of {@code VolumeShaper} is to apply
+ * volume changes by the media time of the player.
+ * Hence, the {@code VolumeShaper} will speed or slow down to
+ * match player changes of playback rate, pause, or resume.
+ *<p>
+ * The {@code OPTION_FLAG_CLOCK_TIME} option allows the {@code VolumeShaper}
+ * progress to be determined by clock time instead of media time.
+ */
+ public static final int OPTION_FLAG_CLOCK_TIME = (1 << 1);
+
+ private static final int OPTION_FLAG_PUBLIC_ALL =
+ OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME;
+
+ /**
+ * A one second linear ramp from silence to full volume.
+ * Use {@link VolumeShaper.Builder#reflectTimes()} to generate
+ * the matching linear duck.
+ */
+ public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder()
+ .setInterpolatorType(INTERPOLATOR_TYPE_LINEAR)
+ .setCurve(new float[] {0.f, 1.f} /* times */,
+ new float[] {0.f, 1.f} /* volumes */)
+ .setDurationMs(1000.)
+ .build();
+
+ /**
+ * A one second cubic ramp from silence to full volume.
+ * Use {@link VolumeShaper.Builder#reflectTimes()} to generate
+ * the matching cubic duck.
+ */
+ public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder()
+ .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
+ .setCurve(new float[] {0.f, 1.f} /* times */,
+ new float[] {0.f, 1.f} /* volumes */)
+ .setDurationMs(1000.)
+ .build();
+
+ /**
+ * A one second sine curve for energy preserving cross fades.
+ * Use {@link VolumeShaper.Builder#reflectTimes()} to generate
+ * the matching cosine duck.
+ */
+ public static final Configuration SINE_RAMP;
+
+ /**
+ * A one second sine-squared s-curve ramp.
+ * Use {@link VolumeShaper.Builder#reflectTimes()}
+ * or {@link VolumeShaper.Builder#invertVolumes()} to generate
+ * the matching s-curve duck.
+ */
+ public static final Configuration SCURVE_RAMP;
+
+ static {
+ final int POINTS = MAXIMUM_CURVE_POINTS;
+ final float times[] = new float[POINTS];
+ final float sines[] = new float[POINTS];
+ final float scurve[] = new float[POINTS];
+ for (int i = 0; i < POINTS; ++i) {
+ times[i] = (float)i / (POINTS - 1);
+ final float sine = (float)Math.sin(times[i] * Math.PI / 2.);
+ sines[i] = sine;
+ scurve[i] = sine * sine;
+ }
+ SINE_RAMP = new VolumeShaper.Configuration.Builder()
+ .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
+ .setCurve(times, sines)
+ .setDurationMs(1000.)
+ .build();
+ SCURVE_RAMP = new VolumeShaper.Configuration.Builder()
+ .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
+ .setCurve(times, scurve)
+ .setDurationMs(1000.)
+ .build();
+ }
+
+ /*
+ * member variables - these are all final
+ */
+
+ // type of VolumeShaper
+ private final int mType;
+
+ // valid when mType is TYPE_ID
+ private final int mId;
+
+ // valid when mType is TYPE_SCALE
+ private final int mInterpolatorType;
+ private final int mOptionFlags;
+ private final double mDurationMs;
+ private final float[] mTimes;
+ private final float[] mVolumes;
+
+ @Override
+ public String toString() {
+ return "VolumeShaper.Configuration["
+ + "mType=" + mType
+ + (mType == TYPE_ID
+ ? ",mId" + mId
+ : ",mInterpolatorType=" + mInterpolatorType
+ + ",mOptionFlags=" + mOptionFlags
+ + ",mDurationMs=" + mDurationMs
+ + ",mTimes[]=" + mTimes
+ + ",mVolumes[]=" + mVolumes
+ + "]");
+ }
+
+ @Override
+ public int hashCode() {
+ return mType == TYPE_ID
+ ? Objects.hash(mType, mId)
+ : Objects.hash(mType, mInterpolatorType, mDurationMs, mTimes, mVolumes);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Configuration)) return false;
+ if (o == this) return true;
+ final Configuration other = (Configuration) o;
+ return mType == other.mType &&
+ (mType == TYPE_ID ? mId == other.mId
+ : mInterpolatorType == other.mInterpolatorType
+ && mDurationMs == other.mDurationMs
+ && mTimes == other.mTimes
+ && mVolumes == other.mVolumes);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mId);
+ if (mType != TYPE_ID) {
+ dest.writeInt(mInterpolatorType);
+ dest.writeInt(mOptionFlags);
+ dest.writeDouble(mDurationMs);
+ dest.writeFloatArray(mTimes);
+ dest.writeFloatArray(mVolumes);
+ }
+ }
+
+ public static final Parcelable.Creator<VolumeShaper.Configuration> CREATOR
+ = new Parcelable.Creator<VolumeShaper.Configuration>() {
+ @Override
+ public VolumeShaper.Configuration createFromParcel(Parcel p) {
+ final int type = p.readInt();
+ final int id = p.readInt();
+ if (type == TYPE_ID) {
+ return new VolumeShaper.Configuration(id);
+ } else {
+ return new VolumeShaper.Configuration(
+ type,
+ id, // id
+ p.readInt(), // interpolatorType
+ p.readInt(), // optionFlags
+ p.readDouble(), // durationMs
+ p.createFloatArray(), // times
+ p.createFloatArray()); // volumes
+ }
+ }
+
+ @Override
+ public VolumeShaper.Configuration[] newArray(int size) {
+ return new VolumeShaper.Configuration[size];
+ }
+ };
+
+ /**
+ * Constructs a volume shaper from an id.
+ *
+ * This is an opaque handle for controlling a {@code VolumeShaper} that has
+ * already been sent to a player. The {@code id} is returned from the
+ * initial {@code setVolumeShaper()} call on success.
+ *
+ * These configurations are for native use only,
+ * they are never returned directly to the user.
+ *
+ * @param id
+ * @throws IllegalArgumentException if id is negative.
+ */
+ private Configuration(int id) {
+ if (id < 0) {
+ throw new IllegalArgumentException("negative id " + id);
+ }
+ mType = TYPE_ID;
+ mId = id;
+ mInterpolatorType = 0;
+ mOptionFlags = 0;
+ mDurationMs = 0;
+ mTimes = null;
+ mVolumes = null;
+ }
+
+ /**
+ * Direct constructor for VolumeShaper.
+ * Use the Builder instead.
+ */
+ private Configuration(@Type int type,
+ int id,
+ @InterpolatorType int interpolatorType,
+ @OptionFlag int optionFlags,
+ double durationMs,
+ @NonNull float[] times,
+ @NonNull float[] volumes) {
+ mType = type;
+ mId = id;
+ mInterpolatorType = interpolatorType;
+ mOptionFlags = optionFlags;
+ mDurationMs = durationMs;
+ // Builder should have cloned these arrays already.
+ mTimes = times;
+ mVolumes = volumes;
+ }
+
+ /**
+ * Returns the {@code VolumeShaper} type.
+ */
+ public @Type int getType() {
+ return mType;
+ }
+
+ /**
+ * @hide
+ * Returns the {@code VolumeShaper} id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the interpolator type.
+ */
+ public @InterpolatorType int getInterpolatorType() {
+ return mInterpolatorType;
+ }
+
+ /**
+ * Returns the option flags
+ */
+ public @OptionFlag int getOptionFlags() {
+ return mOptionFlags & OPTION_FLAG_PUBLIC_ALL;
+ }
+
+ /* package */ @OptionFlag int getAllOptionFlags() {
+ return mOptionFlags;
+ }
+
+ /**
+ * Returns the duration of the effect in milliseconds.
+ */
+ public double getDurationMs() {
+ return mDurationMs;
+ }
+
+ /**
+ * Returns the times (x) coordinate array of the volume curve points.
+ */
+ public float[] getTimes() {
+ return mTimes;
+ }
+
+ /**
+ * Returns the volumes (y) coordinate array of the volume curve points.
+ */
+ public float[] getVolumes() {
+ return mVolumes;
+ }
+
+ /**
+ * Checks the validity of times and volumes point representation.
+ *
+ * {@code times[]} and {@code volumes[]} are two arrays representing points
+ * for the volume curve.
+ *
+ * @param times the x coordinates for the points,
+ * must be between 0.f and 1.f and be monotonic.
+ * @param volumes the y coordinates for the points,
+ * must be between 0.f and 1.f for linear and
+ * must be no greater than 0.f for log (dBFS).
+ * @param log set to true if the scale is logarithmic.
+ * @return null if no error, or the reason in a {@code String} for an error.
+ */
+ private static @Nullable String checkCurveForErrors(
+ @NonNull float[] times, @NonNull float[] volumes, boolean log) {
+ if (times.length != volumes.length) {
+ return "array length must match";
+ } else if (times.length < 2) {
+ return "array length must be at least 2";
+ } else if (times.length > MAXIMUM_CURVE_POINTS) {
+ return "array length must be no larger than " + MAXIMUM_CURVE_POINTS;
+ } else if (times[0] != 0.f) {
+ return "times must start at 0.f";
+ } else if (times[times.length - 1] != 1.f) {
+ return "times must end at 1.f";
+ }
+
+ // validate points along the curve
+ for (int i = 1; i < times.length; ++i) {
+ if (!(times[i] > times[i - 1]) /* handle nan */) {
+ return "times not monotonic increasing, check index " + i;
+ }
+ }
+ if (log) {
+ for (int i = 0; i < volumes.length; ++i) {
+ if (!(volumes[i] <= 0.f) /* handle nan */) {
+ return "volumes for log scale cannot be positive, "
+ + "check index " + i;
+ }
+ }
+ } else {
+ for (int i = 0; i < volumes.length; ++i) {
+ if (!(volumes[i] >= 0.f) || !(volumes[i] <= 1.f) /* handle nan */) {
+ return "volumes for linear scale must be between 0.f and 1.f, "
+ + "check index " + i;
+ }
+ }
+ }
+ return null; // no errors
+ }
+
+ private static void checkValidVolume(float volume, boolean log) {
+ if (log) {
+ if (!(volume <= 0.f) /* handle nan */) {
+ throw new IllegalArgumentException("dbfs volume must be 0.f or less");
+ }
+ } else {
+ if (!(volume >= 0.f) || !(volume <= 1.f) /* handle nan */) {
+ throw new IllegalArgumentException("volume must be >= 0.f and <= 1.f");
+ }
+ }
+ }
+
+ private static void clampVolume(float[] volumes, boolean log) {
+ if (log) {
+ for (int i = 0; i < volumes.length; ++i) {
+ if (!(volumes[i] <= 0.f) /* handle nan */) {
+ volumes[i] = 0.f;
+ }
+ }
+ } else {
+ for (int i = 0; i < volumes.length; ++i) {
+ if (!(volumes[i] >= 0.f) /* handle nan */) {
+ volumes[i] = 0.f;
+ } else if (!(volumes[i] <= 1.f)) {
+ volumes[i] = 1.f;
+ }
+ }
+ }
+ }
+
+ /**
+ * Builder class for a {@link VolumeShaper.Configuration} object.
+ * <p> Here is an example where {@code Builder} is used to define the
+ * {@link VolumeShaper.Configuration}.
+ *
+ * <pre class="prettyprint">
+ * VolumeShaper.Configuration LINEAR_RAMP =
+ * new VolumeShaper.Configuration.Builder()
+ * .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+ * .setCurve(new float[] { 0.f, 1.f }, // times
+ * new float[] { 0.f, 1.f }) // volumes
+ * .setDurationMs(1000.)
+ * .build();
+ * </pre>
+ * <p>
+ */
+ public static final class Builder {
+ private int mType = TYPE_SCALE;
+ private int mId = -1; // invalid
+ private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC;
+ private int mOptionFlags = 0;
+ private double mDurationMs = 1000.;
+ private float[] mTimes = null;
+ private float[] mVolumes = null;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given {@code VolumeShaper.Configuration}
+ * @param configuration prototypical configuration
+ * which will be reused in the new Builder.
+ */
+ public Builder(@NonNull Configuration configuration) {
+ mType = configuration.getType();
+ mId = configuration.getId();
+ mOptionFlags = configuration.getAllOptionFlags();
+ mInterpolatorType = configuration.getInterpolatorType();
+ mDurationMs = configuration.getDurationMs();
+ mTimes = configuration.getTimes();
+ mVolumes = configuration.getVolumes();
+ }
+
+ /**
+ * @hide
+ * TODO make SystemApi
+ *
+ * Set the id for system defined shapers.
+ * @param id
+ * @return
+ */
+ public @NonNull Builder setId(int id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Sets the interpolator type.
+ *
+ * If omitted the interplator type is {@link #INTERPOLATOR_TYPE_CUBIC}.
+ *
+ * @param interpolatorType method of interpolation used for the volume curve.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if {@code interpolatorType} is not valid.
+ */
+ public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) {
+ switch (interpolatorType) {
+ case INTERPOLATOR_TYPE_STEP:
+ case INTERPOLATOR_TYPE_LINEAR:
+ case INTERPOLATOR_TYPE_CUBIC:
+ case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
+ mInterpolatorType = interpolatorType;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid interpolatorType: "
+ + interpolatorType);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the optional flags
+ *
+ * If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has
+ * changed the volume curve needs to be set again as the acceptable
+ * volume domain has changed.
+ *
+ * @param optionFlags new value to replace the old {@code optionFlags}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if flag is not recognized.
+ */
+ public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) {
+ if ((optionFlags & ~OPTION_FLAG_PUBLIC_ALL) != 0) {
+ throw new IllegalArgumentException("invalid bits in flag: " + optionFlags);
+ }
+ mOptionFlags = mOptionFlags & ~OPTION_FLAG_PUBLIC_ALL | optionFlags;
+ return this;
+ }
+
+ /**
+ * Sets the volume shaper duration in milliseconds.
+ *
+ * If omitted, the default duration is 1 second.
+ *
+ * @param durationMs
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if duration is not positive.
+ */
+ public @NonNull Builder setDurationMs(double durationMs) {
+ if (durationMs <= 0.) {
+ throw new IllegalArgumentException(
+ "duration: " + durationMs + " not positive");
+ }
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Sets the volume curve.
+ *
+ * The volume curve is represented by a set of control points given by
+ * two float arrays of equal length,
+ * one representing the time (x) coordinates
+ * and one corresponding to the volume (y) coordinates.
+ * The length must be at least 2
+ * and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}.
+ * <p>
+ * The volume curve is normalized as follows:
+ * (1) time (x) coordinates should be monotonically increasing, from 0.f to 1.f;
+ * (2) volume (y) coordinates must be within 0.f to 1.f for linear and be non-positive
+ * for log scaling.
+ * <p>
+ * The time scale is set by {@link #setDurationMs} in seconds.
+ * <p>
+ * @param times an array of float values representing
+ * the time line of the volume curve.
+ * @param volumes an array of float values representing
+ * the amplitude of the volume curve.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid.
+ */
+ public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) {
+ String error = checkCurveForErrors(
+ times, volumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
+ if (error != null) {
+ throw new IllegalArgumentException(error);
+ }
+ mTimes = times.clone();
+ mVolumes = volumes.clone();
+ return this;
+ }
+
+ /**
+ * Reflects the volume curve so that
+ * the shaper changes volume from the end
+ * to the start.
+ *
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder reflectTimes() {
+ int i;
+ for (i = 0; i < mTimes.length / 2; ++i) {
+ float temp = mTimes[0];
+ mTimes[i] = 1.f - mTimes[mTimes.length - 1 - i];
+ mTimes[mTimes.length - 1 - i] = 1.f - temp;
+ }
+ if ((mTimes.length & 1) != 0) {
+ mTimes[i] = 1.f - mTimes[i];
+ }
+ return this;
+ }
+
+ /**
+ * Inverts the volume curve so that the max volume
+ * becomes the min volume and vice versa.
+ *
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder invertVolumes() {
+ if (mVolumes.length >= 2) {
+ float min = mVolumes[0];
+ float max = mVolumes[0];
+ for (int i = 1; i < mVolumes.length; ++i) {
+ if (mVolumes[i] < min) {
+ min = mVolumes[i];
+ } else if (mVolumes[i] > max) {
+ max = mVolumes[i];
+ }
+ }
+
+ final float maxmin = max + min;
+ for (int i = 0; i < mVolumes.length; ++i) {
+ mVolumes[i] = maxmin - mVolumes[i];
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Scale the curve end volume to a target value.
+ *
+ * Keeps the start volume the same.
+ * This works best if the volume curve is monotonic.
+ *
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if volume is not valid.
+ */
+ public @NonNull Builder scaleToEndVolume(float volume) {
+ final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
+ checkValidVolume(volume, log);
+ final float startVolume = mVolumes[0];
+ final float endVolume = mVolumes[mVolumes.length - 1];
+ if (endVolume == startVolume) {
+ // match with linear ramp
+ final float offset = volume - startVolume;
+ for (int i = 0; i < mVolumes.length; ++i) {
+ mVolumes[i] = mVolumes[i] + offset * mTimes[i];
+ }
+ } else {
+ // scale
+ final float scale = (volume - startVolume) / (endVolume - startVolume);
+ for (int i = 0; i < mVolumes.length; ++i) {
+ mVolumes[i] = scale * (mVolumes[i] - startVolume) + startVolume;
+ }
+ }
+ clampVolume(mVolumes, log);
+ return this;
+ }
+
+ /**
+ * Scale the curve start volume to a target value.
+ *
+ * Keeps the end volume the same.
+ * This works best if the volume curve is monotonic.
+ *
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if volume is not valid.
+ */
+ public @NonNull Builder scaleToStartVolume(float volume) {
+ final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
+ checkValidVolume(volume, log);
+ final float startVolume = mVolumes[0];
+ final float endVolume = mVolumes[mVolumes.length - 1];
+ if (endVolume == startVolume) {
+ // match with linear ramp
+ final float offset = volume - startVolume;
+ for (int i = 0; i < mVolumes.length; ++i) {
+ mVolumes[i] = mVolumes[i] + offset * (1.f - mTimes[i]);
+ }
+ } else {
+ final float scale = (volume - endVolume) / (startVolume - endVolume);
+ for (int i = 0; i < mVolumes.length; ++i) {
+ mVolumes[i] = scale * (mVolumes[i] - endVolume) + endVolume;
+ }
+ }
+ clampVolume(mVolumes, log);
+ return this;
+ }
+
+ /**
+ * Builds a new {@link VolumeShaper} object.
+ *
+ * @return a new {@link VolumeShaper} object
+ */
+ public @NonNull Configuration build() {
+ String error = checkCurveForErrors(
+ mTimes, mVolumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0);
+ if (error != null) {
+ throw new IllegalArgumentException(error);
+ }
+ return new Configuration(mType, mId, mInterpolatorType, mOptionFlags,
+ mDurationMs, mTimes, mVolumes);
+ }
+ } // Configuration.Builder
+ } // Configuration
+
+ /**
+ * The {@code VolumeShaper.Operation} class is used to specify operations
+ * to the {@code VolumeShaper} that affect the volume change.
+ */
+ public static final class Operation implements Parcelable {
+ /**
+ * Forward playback from current volume time position.
+ */
+ public static final Operation PLAY =
+ new VolumeShaper.Operation.Builder()
+ .build();
+
+ /**
+ * Reverse playback from current volume time position.
+ */
+ public static final Operation REVERSE =
+ new VolumeShaper.Operation.Builder()
+ .reverse()
+ .build();
+
+ // No user serviceable parts below.
+
+ // These flags must match the native VolumeShaper::Operation::Flag
+ /** @hide */
+ @IntDef({
+ FLAG_NONE,
+ FLAG_REVERSE,
+ FLAG_TERMINATE,
+ FLAG_JOIN,
+ FLAG_DEFER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flag {}
+
+ /**
+ * No special {@code VolumeShaper} operation.
+ */
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Reverse the {@code VolumeShaper} progress.
+ *
+ * Reverses the {@code VolumeShaper} curve from its current
+ * position. If the {@code VolumeShaper} curve has not started,
+ * it automatically is considered finished.
+ */
+ private static final int FLAG_REVERSE = 1 << 0;
+
+ /**
+ * Terminate the existing {@code VolumeShaper}.
+ * This flag is generally used by itself;
+ * it takes precedence over all other flags.
+ */
+ private static final int FLAG_TERMINATE = 1 << 1;
+
+ /**
+ * Attempt to join as best as possible to the previous {@code VolumeShaper}.
+ * This requires the previous {@code VolumeShaper} to be active and
+ * {@link #setReplaceId} to be set.
+ */
+ private static final int FLAG_JOIN = 1 << 2;
+
+ /**
+ * Defer playback until next operation is sent. This is used
+ * when starting a VolumeShaper effect.
+ */
+ private static final int FLAG_DEFER = 1 << 3;
+
+ private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE;
+
+ private final int mFlags;
+ private final int mReplaceId;
+
+ @Override
+ public String toString() {
+ return "VolumeShaper.Operation["
+ + "mFlags=" + mFlags
+ + ",mReplaceId" + mReplaceId
+ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFlags, mReplaceId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Operation)) return false;
+ if (o == this) return true;
+ final Operation other = (Operation) o;
+ return mFlags == other.mFlags
+ && mReplaceId == other.mReplaceId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFlags);
+ dest.writeInt(mReplaceId);
+ }
+
+ public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR
+ = new Parcelable.Creator<VolumeShaper.Operation>() {
+ @Override
+ public VolumeShaper.Operation createFromParcel(Parcel p) {
+ return new VolumeShaper.Operation(
+ p.readInt() // flags
+ , p.readInt()); // replaceId
+ }
+
+ @Override
+ public VolumeShaper.Operation[] newArray(int size) {
+ return new VolumeShaper.Operation[size];
+ }
+ };
+
+ private Operation(@Flag int flags, int replaceId) {
+ mFlags = flags;
+ mReplaceId = replaceId;
+ }
+
+ /**
+ * @hide
+ * {@code Builder} class for {@link VolumeShaper.Operation} object.
+ *
+ * Not for public use.
+ */
+ public static final class Builder {
+ int mFlags;
+ int mReplaceId;
+
+ /**
+ * Constructs a new {@code Builder} with the defaults.
+ */
+ public Builder() {
+ mFlags = 0;
+ mReplaceId = -1;
+ }
+
+ /**
+ * Constructs a new Builder from a given {@code VolumeShaper.Operation}
+ * @param operation the {@code VolumeShaper.operation} whose data will be
+ * reused in the new Builder.
+ */
+ public Builder(@NonNull VolumeShaper.Operation operation) {
+ mReplaceId = operation.mReplaceId;
+ mFlags = operation.mFlags;
+ }
+
+ /**
+ * Replaces the previous {@code VolumeShaper}.
+ * It has no other effect if the {@code VolumeShaper} is
+ * already expired. If the replaceId is the same as the id associated with
+ * the {@code VolumeShaper} in a {@code setVolumeShaper()} call,
+ * an error is returned.
+ * @param handle is a previous volumeShaper {@code VolumeShaper}.
+ * @param join the start to match the current volume of the previous
+ * shaper.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder replace(int id, boolean join) {
+ mReplaceId = id;
+ if (join) {
+ mFlags |= FLAG_JOIN;
+ } else {
+ mFlags &= ~FLAG_JOIN;
+ }
+ return this;
+ }
+
+ /**
+ * Defers all operations.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder defer() {
+ mFlags |= FLAG_DEFER;
+ return this;
+ }
+
+ /**
+ * Terminates the VolumeShaper.
+ * Do not call directly, use {@link VolumeShaper#release()}.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder terminate() {
+ mFlags |= FLAG_TERMINATE;
+ return this;
+ }
+
+ /**
+ * Reverses direction.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder reverse() {
+ mFlags ^= FLAG_REVERSE;
+ return this;
+ }
+
+ /**
+ * Sets the operation flag. Do not call this directly but one of the
+ * other builder methods.
+ *
+ * @param flags new value for {@code flags}, consisting of ORed flags.
+ * @return the same Builder instance.
+ */
+ private @NonNull Builder setFlags(@Flag int flags) {
+ if ((flags & ~FLAG_PUBLIC_ALL) != 0) {
+ throw new IllegalArgumentException("flag has unknown bits set: " + flags);
+ }
+ mFlags = mFlags & ~FLAG_PUBLIC_ALL | flags;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link VolumeShaper.Operation} object.
+ *
+ * @return a new {@code VolumeShaper.Operation} object
+ */
+ public @NonNull Operation build() {
+ return new Operation(mFlags, mReplaceId);
+ }
+ } // Operation.Builder
+ } // Operation
+
+ /**
+ * @hide
+ * {@code VolumeShaper.State} represents the current progress
+ * of the {@code VolumeShaper}.
+ *
+ * Not for public use.
+ */
+ public static final class State implements Parcelable {
+ private float mVolume;
+ private float mXOffset;
+
+ @Override
+ public String toString() {
+ return "VolumeShaper.State["
+ + "mVolume=" + mVolume
+ + ",mXOffset" + mXOffset
+ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mVolume, mXOffset);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof State)) return false;
+ if (o == this) return true;
+ final State other = (State) o;
+ return mVolume == other.mVolume
+ && mXOffset == other.mXOffset;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mVolume);
+ dest.writeFloat(mXOffset);
+ }
+
+ public static final Parcelable.Creator<VolumeShaper.State> CREATOR
+ = new Parcelable.Creator<VolumeShaper.State>() {
+ @Override
+ public VolumeShaper.State createFromParcel(Parcel p) {
+ return new VolumeShaper.State(
+ p.readFloat() // volume
+ , p.readFloat()); // xOffset
+ }
+
+ @Override
+ public VolumeShaper.State[] newArray(int size) {
+ return new VolumeShaper.State[size];
+ }
+ };
+
+ /* package */ State(float volume, float xOffset) {
+ mVolume = volume;
+ mXOffset = xOffset;
+ }
+
+ /**
+ * Gets the volume of the {@link VolumeShaper.State}.
+ */
+ public float getVolume() {
+ return mVolume;
+ }
+
+ /**
+ * Gets the elapsed ms of the {@link VolumeShaper.State}
+ */
+ public double getXOffset() {
+ return mXOffset;
+ }
+ } // State
+}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index ddbd542e..45f88f3 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1977,6 +1977,24 @@
public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
/**
+ * The flag indicating whether this TV program is browsable or not.
+ *
+ * <p>This column can only be set by system apps. For other applications, it is a read-only
+ * column. Trying to modify it may cause {@link SecurityException}.
+ *
+ * <p>A value of 1 indicates that the program is browsable and can be shown to users in
+ * the UI. A value of 0 indicates that the program should be hidden from users and the
+ * application who changes this value to 0 should send
+ * {@link TvInputManager#ACTION_PROGRAM_BROWSABLE_DISABLED} to the owner of the program
+ * to notify this change.
+ *
+ * <p>This value is set to 1 (browsable) by default.
+ *
+ * <p>Type: INTEGER (boolean)
+ */
+ public static final String COLUMN_BROWSABLE = "browsable";
+
+ /**
* The internal ID used by individual TV input services.
*
* <p>This is internal to the provider that inserted it, and should not be decoded by other
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index b630270..4c2b031 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -325,23 +325,39 @@
"android.media.tv.action.VIEW_RECORDING_SCHEDULES";
/**
+ * Action sent by the system to tell the target TV input that one of its program's browsable
+ * state is disabled, i.e., it will no longer be shown to users, which, for example, might
+ * be a result of users' interaction with UI.
+ *
+ * <p>The intent must contain the following bundle parameter:
+ * <ul>
+ * <li>{@link #EXTRA_PROGRAM_ID} the program ID as a long integer.
+ * </ul>
+ */
+ public static final String ACTION_PROGRAM_BROWSABLE_DISABLED =
+ "android.media.tv.action.PROGRAM_BROWSABLE_DISABLED";
+
+ /**
* Action sent by an application telling the system to set the given channel as browsable.
*
* <p>The intent must contain the following bundle parameters:
* <ul>
- * <li>{@link #EXTRA_CHANNEL_ID} then channel ID as an integer.
+ * <li>{@link #EXTRA_CHANNEL_ID} the channel ID as a long integer.
* <li>{@link #EXTRA_PACKAGE_NAME} the package name of the requesting application.
* </ul>
*/
public static final String ACTION_MAKE_CHANNEL_BROWSABLE
= "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
- /** The key for a bundle parameter containing a channel ID as an integer */
+ /** The key for a bundle parameter containing a channel ID as a long integer */
public static final String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
/** The key for a bundle parameter containing a package name as a string. */
public static final String EXTRA_PACKAGE_NAME = "android.media.tv.extra.PACKAGE_NAME";
+ /** The key for a bundle parameter containing a program ID as a long integer */
+ public static final String EXTRA_PROGRAM_ID = "android.media.tv.extra.PROGRAM_ID";
+
private final ITvInputManager mService;
private final Object mLock = new Object();
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 5e8135f..636727e 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -44,6 +44,7 @@
#include "android_media_MediaMetricsJNI.h"
#include "android_media_PlaybackParams.h"
#include "android_media_SyncParams.h"
+#include "android_media_VolumeShaper.h"
#include "android_media_Utils.h"
#include "android_os_Parcel.h"
@@ -57,86 +58,20 @@
#include "android_util_Binder.h"
// Modular DRM begin
-#include <media/drm/DrmAPI.h>
-
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! (var), "Unable to find class " className);
-#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
-var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
-LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-
#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find method " fieldName);
-#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
-var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \
-LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-
-
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-struct RequestFields {
- jfieldID data;
- jfieldID defaultUrl;
- jfieldID requestType;
-};
-
-struct HashmapFields {
- jmethodID init;
- jmethodID get;
- jmethodID put;
- jmethodID entrySet;
-};
-
-struct SetFields {
- jmethodID iterator;
-};
-
-struct IteratorFields {
- jmethodID next;
- jmethodID hasNext;
-};
-
-struct EntryFields {
- jmethodID getKey;
- jmethodID getValue;
-};
-
-struct KeyTypes {
- jint kKeyTypeStreaming;
- jint kKeyTypeOffline;
- jint kKeyTypeRelease;
-};
-
-static KeyTypes gKeyTypes;
-
-struct KeyRequestTypes {
- jint kKeyRequestTypeInitial;
- jint kKeyRequestTypeRenewal;
- jint kKeyRequestTypeRelease;
-};
-
-static KeyRequestTypes gKeyRequestTypes;
-
struct StateExceptionFields {
jmethodID init;
jclass classId;
};
-struct drm_fields_t {
- RequestFields keyRequest;
- HashmapFields hashmap;
- SetFields set;
- IteratorFields iterator;
- EntryFields entry;
- StateExceptionFields stateException;
- jclass stringClassId;
-};
-
-static drm_fields_t gFields;
-
+static StateExceptionFields gStateExceptionFields;
// Modular DRM end
// ----------------------------------------------------------------------------
@@ -160,6 +95,7 @@
static BufferingParams::fields_t gBufferingParamsFields;
static PlaybackParams::fields_t gPlaybackParamsFields;
static SyncParams::fields_t gSyncParamsFields;
+static VolumeShaperHelper::fields_t gVolumeShaperFields;
static Mutex sLock;
@@ -1039,55 +975,20 @@
gBufferingParamsFields.init(env);
// Modular DRM
- FIND_CLASS(clazz, "android/media/MediaDrm");
- if (clazz) {
- jfieldID field;
- GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
- gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
- GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
- gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field);
- GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
- gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
-
- env->DeleteLocalRef(clazz);
- } else {
- ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
- "get clazz android/media/MediaDrm");
- }
-
- FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
- if (clazz) {
- GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
- GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
- GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I");
-
- jfieldID field;
- GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
- gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
- GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
- gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
- GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
- gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
-
- env->DeleteLocalRef(clazz);
- } else {
- ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
- "get clazz android/media/MediaDrm$KeyRequest");
- }
-
FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
if (clazz) {
- GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
- gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
env->DeleteLocalRef(clazz);
} else {
- ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ ALOGE("JNI android_media_MediaPlayer_native_init couldn't "
"get clazz android/media/MediaDrm$MediaDrmStateException");
}
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
+ gVolumeShaperFields.init(env);
}
static void
@@ -1259,6 +1160,51 @@
;
}
+// Pass through the arguments to the MediaServer player implementation.
+static jint android_media_MediaPlayer_applyVolumeShaper(JNIEnv *env, jobject thiz,
+ jobject jconfig, jobject joperation) {
+ // NOTE: hard code here to prevent platform issues. Must match VolumeShaper.java
+ const int VOLUME_SHAPER_INVALID_OPERATION = -38;
+
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == nullptr) {
+ return (jint)VOLUME_SHAPER_INVALID_OPERATION;
+ }
+
+ sp<VolumeShaper::Configuration> configuration;
+ sp<VolumeShaper::Operation> operation;
+ if (jconfig != nullptr) {
+ configuration = VolumeShaperHelper::convertJobjectToConfiguration(
+ env, gVolumeShaperFields, jconfig);
+ ALOGV("applyVolumeShaper configuration: %s", configuration->toString().c_str());
+ }
+ if (joperation != nullptr) {
+ operation = VolumeShaperHelper::convertJobjectToOperation(
+ env, gVolumeShaperFields, joperation);
+ ALOGV("applyVolumeShaper operation: %s", operation->toString().c_str());
+ }
+ VolumeShaper::Status status = mp->applyVolumeShaper(configuration, operation);
+ if (status == INVALID_OPERATION) {
+ status = VOLUME_SHAPER_INVALID_OPERATION;
+ }
+ return (jint)status; // if status < 0 an error, else a VolumeShaper id
+}
+
+// Pass through the arguments to the MediaServer player implementation.
+static jobject android_media_MediaPlayer_getVolumeShaperState(JNIEnv *env, jobject thiz,
+ jint id) {
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == nullptr) {
+ return (jobject)nullptr;
+ }
+
+ sp<VolumeShaper::State> state = mp->getVolumeShaperState((int)id);
+ if (state.get() == nullptr) {
+ return (jobject)nullptr;
+ }
+ return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state);
+}
+
/////////////////////////////////////////////////////////////////////////////////////
// Modular DRM begin
@@ -1267,8 +1213,8 @@
{
ALOGE("Illegal DRM state exception: %s (%d)", msg, err);
- jobject exception = env->NewObject(gFields.stateException.classId,
- gFields.stateException.init, static_cast<int>(err),
+ jobject exception = env->NewObject(gStateExceptionFields.classId,
+ gStateExceptionFields.init, static_cast<int>(err),
env->NewStringUTF(msg));
env->Throw(static_cast<jthrowable>(exception));
}
@@ -1345,18 +1291,6 @@
return false;
}
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector)
-{
- size_t length = vector.size();
- jbyteArray result = env->NewByteArray(length);
- if (result != NULL) {
- env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array());
- }
- return result;
-}
-
-// TODO: investigate if these can be shared with their MediaDrm counterparts
static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray)
{
Vector<uint8_t> vector;
@@ -1366,74 +1300,8 @@
return vector;
}
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-static String8 JStringToString8(JNIEnv *env, jstring const &jstr)
-{
- String8 result;
-
- const char *s = env->GetStringUTFChars(jstr, NULL);
- if (s) {
- result = s;
- env->ReleaseStringUTFChars(jstr, s);
- }
- return result;
-}
-
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env,
- jobject &hashMap, bool* pIsOK)
-{
- jclass clazz = gFields.stringClassId;
- KeyedVector<String8, String8> keyedVector;
- *pIsOK = true;
-
- jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
- if (entrySet) {
- jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator);
- if (iterator) {
- jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
- while (hasNext) {
- jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next);
- if (entry) {
- jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey);
- if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "HashMap key is not a String");
- env->DeleteLocalRef(entry);
- *pIsOK = false;
- break;
- }
- jstring jkey = static_cast<jstring>(obj);
-
- obj = env->CallObjectMethod(entry, gFields.entry.getValue);
- if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "HashMap value is not a String");
- env->DeleteLocalRef(entry);
- *pIsOK = false;
- break;
- }
- jstring jvalue = static_cast<jstring>(obj);
-
- String8 key = JStringToString8(env, jkey);
- String8 value = JStringToString8(env, jvalue);
- keyedVector.add(key, value);
-
- env->DeleteLocalRef(jkey);
- env->DeleteLocalRef(jvalue);
- hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
- }
- env->DeleteLocalRef(entry);
- }
- env->DeleteLocalRef(iterator);
- }
- env->DeleteLocalRef(entrySet);
- }
- return keyedVector;
-}
-
static void android_media_MediaPlayer_prepareDrm(JNIEnv *env, jobject thiz,
- jbyteArray uuidObj, jint mode)
+ jbyteArray uuidObj, jbyteArray drmSessionIdObj)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL) {
@@ -1456,13 +1324,23 @@
return;
}
- status_t err = mp->prepareDrm(uuid.array(), mode);
+ Vector<uint8_t> drmSessionId = JByteArrayToVector(env, drmSessionIdObj);
+
+ if (drmSessionId.size() == 0) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ "empty drmSessionId");
+ return;
+ }
+
+ status_t err = mp->prepareDrm(uuid.array(), drmSessionId);
if (err != OK) {
if (err == INVALID_OPERATION) {
jniThrowException(
env,
"java/lang/IllegalStateException",
- "The player is not prepared yet.");
+ "The player must be in prepared state.");
} else if (err == ERROR_DRM_CANNOT_HANDLE) {
jniThrowException(
env,
@@ -1488,211 +1366,10 @@
jniThrowException(
env,
"java/lang/IllegalStateException",
- "The player is not prepared yet.");
+ "Can not release DRM in an active player state.");
}
}
}
-
-static jobject android_media_MediaPlayer_getKeyRequest(JNIEnv *env, jobject thiz, jbyteArray jscope,
- jstring jmimeType, jint jkeyType, jobject joptParams)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return NULL;
- }
-
- Vector<uint8_t> scope;
- if (jscope != NULL) {
- scope = JByteArrayToVector(env, jscope);
- }
-
- String8 mimeType;
- if (jmimeType != NULL) {
- mimeType = JStringToString8(env, jmimeType);
- }
-
- DrmPlugin::KeyType keyType;
- if (jkeyType == gKeyTypes.kKeyTypeStreaming) {
- keyType = DrmPlugin::kKeyType_Streaming;
- } else if (jkeyType == gKeyTypes.kKeyTypeOffline) {
- keyType = DrmPlugin::kKeyType_Offline;
- } else if (jkeyType == gKeyTypes.kKeyTypeRelease) {
- keyType = DrmPlugin::kKeyType_Release;
- } else {
- jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
- return NULL;
- }
-
- KeyedVector<String8, String8> optParams;
- if (joptParams != NULL) {
- bool isOK;
- optParams = HashMapToKeyedVector(env, joptParams, &isOK);
- if (!isOK) {
- return NULL;
- }
- }
-
- Vector<uint8_t> request;
- String8 defaultUrl;
- DrmPlugin::KeyRequestType keyRequestType;
- status_t err = mp->getKeyRequest(scope, mimeType, keyType, optParams, request, defaultUrl,
- keyRequestType);
-
- if (throwDrmExceptionAsNecessary(env, err, "Failed to get key request")) {
- return NULL;
- }
-
- ALOGV("JNI getKeyRequest err %d request %d url %s keyReqType %d",
- err, (int)request.size(), defaultUrl.string(), (int)keyRequestType);
-
- // Fill out return obj
- jclass clazz;
- FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
-
- jobject keyObj = NULL;
-
- if (clazz) {
- keyObj = env->AllocObject(clazz);
- jbyteArray jrequest = VectorToJByteArray(env, request);
- env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest);
-
- jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
- env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
-
- switch (keyRequestType) {
- case DrmPlugin::kKeyRequestType_Initial:
- env->SetIntField(keyObj, gFields.keyRequest.requestType,
- gKeyRequestTypes.kKeyRequestTypeInitial);
- break;
- case DrmPlugin::kKeyRequestType_Renewal:
- env->SetIntField(keyObj, gFields.keyRequest.requestType,
- gKeyRequestTypes.kKeyRequestTypeRenewal);
- break;
- case DrmPlugin::kKeyRequestType_Release:
- env->SetIntField(keyObj, gFields.keyRequest.requestType,
- gKeyRequestTypes.kKeyRequestTypeRelease);
- break;
- default:
- throwDrmStateException(env, "MediaPlayer/DRM plugin failure: unknown "
- "key request type", ERROR_DRM_UNKNOWN);
- break;
- }
- }
-
- return keyObj;
-}
-
-static jbyteArray android_media_MediaPlayer_provideKeyResponse(JNIEnv *env, jobject thiz,
- jbyteArray jreleaseKeySetId, jbyteArray jresponse)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL ) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return NULL;
- }
-
- if (jresponse == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "key response is null");
- return NULL;
- }
-
- Vector<uint8_t> releaseKeySetId;
- if (jreleaseKeySetId != NULL) {
- releaseKeySetId = JByteArrayToVector(env, jreleaseKeySetId);
- }
-
- Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
- Vector<uint8_t> keySetId;
-
- status_t err = mp->provideKeyResponse(releaseKeySetId, response, keySetId);
-
- if (throwDrmExceptionAsNecessary(env, err, "Failed to handle key response")) {
- return NULL;
- }
- return VectorToJByteArray(env, keySetId);
-}
-
-static void android_media_MediaPlayer_restoreKeys(JNIEnv *env, jobject thiz, jbyteArray jkeySetId)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return;
- }
-
- if (jkeySetId == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
- return;
- }
-
- Vector<uint8_t> keySetId;
- keySetId = JByteArrayToVector(env, jkeySetId);
-
- status_t err = mp->restoreKeys(keySetId);
-
- ALOGV("JNI restoreKeys err %d ", err);
- throwDrmExceptionAsNecessary(env, err, "Failed to restore keys");
-}
-
-static jstring android_media_MediaPlayer_getDrmPropertyString(JNIEnv *env, jobject thiz,
- jstring jname)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return NULL;
- }
-
- if (jname == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "property name String is null");
- return NULL;
- }
-
- String8 name = JStringToString8(env, jname);
- String8 value;
-
- status_t err = mp->getDrmPropertyString(name, value);
-
- ALOGV("JNI getPropertyString err %d", err);
-
- if (throwDrmExceptionAsNecessary(env, err, "Failed to get property")) {
- return NULL;
- }
-
- return env->NewStringUTF(value.string());
-}
-
-static void android_media_MediaPlayer_setDrmPropertyString(JNIEnv *env, jobject thiz,
- jstring jname, jstring jvalue)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return;
- }
-
- if (jname == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "property name String is null");
- return;
- }
-
- if (jvalue == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "property value String is null");
- return;
- }
-
- String8 name = JStringToString8(env, jname);
- String8 value = JStringToString8(env, jvalue);
-
- status_t err = mp->setDrmPropertyString(name, value);
-
- ALOGV("JNI setPropertyString err %d", err);
- throwDrmExceptionAsNecessary(env, err, "Failed to set property");
-}
// Modular DRM end
// ----------------------------------------------------------------------------
@@ -1747,15 +1424,15 @@
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
+ {"native_applyVolumeShaper",
+ "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
+ (void *)android_media_MediaPlayer_applyVolumeShaper},
+ {"native_getVolumeShaperState",
+ "(I)Landroid/media/VolumeShaper$State;",
+ (void *)android_media_MediaPlayer_getVolumeShaperState},
// Modular DRM
- { "_prepareDrm", "([BI)V", (void *)android_media_MediaPlayer_prepareDrm },
+ { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer_prepareDrm },
{ "_releaseDrm", "()V", (void *)android_media_MediaPlayer_releaseDrm },
- { "_getKeyRequest", "([BLjava/lang/String;ILjava/util/Map;)" "Landroid/media/MediaDrm$KeyRequest;",
- (void *)android_media_MediaPlayer_getKeyRequest },
- { "_provideKeyResponse", "([B[B)[B", (void *)android_media_MediaPlayer_provideKeyResponse },
- { "_getDrmPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_MediaPlayer_getDrmPropertyString },
- { "_setDrmPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDrmPropertyString },
- { "_restoreKeys", "([B)V", (void *)android_media_MediaPlayer_restoreKeys },
};
// This function only registers the native methods
diff --git a/media/jni/android_media_VolumeShaper.h b/media/jni/android_media_VolumeShaper.h
new file mode 100644
index 0000000..dbbc478
--- /dev/null
+++ b/media/jni/android_media_VolumeShaper.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright 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_VOLUME_SHAPER_H_
+#define _ANDROID_MEDIA_VOLUME_SHAPER_H_
+
+#include <media/VolumeShaper.h>
+
+namespace android {
+
+// This entire class is inline as it is used from both core and media
+struct VolumeShaperHelper {
+ struct fields_t {
+ // VolumeShaper.Configuration
+ jclass coClazz;
+ jmethodID coConstructId;
+ jfieldID coTypeId;
+ jfieldID coIdId;
+ jfieldID coInterpolatorTypeId;
+ jfieldID coOptionFlagsId;
+ jfieldID coDurationMsId;
+ jfieldID coTimesId;
+ jfieldID coVolumesId;
+
+ // VolumeShaper.Operation
+ jclass opClazz;
+ jmethodID opConstructId;
+ jfieldID opFlagsId;
+ jfieldID opReplaceIdId;
+
+ // VolumeShaper.State
+ jclass stClazz;
+ jmethodID stConstructId;
+ jfieldID stVolumeId;
+ jfieldID stXOffsetId;
+
+ void init(JNIEnv *env) {
+ jclass lclazz = env->FindClass("android/media/VolumeShaper$Configuration");
+ if (lclazz == nullptr) {
+ return;
+ }
+ coClazz = (jclass)env->NewGlobalRef(lclazz);
+ if (coClazz == nullptr) {
+ return;
+ }
+ coConstructId = env->GetMethodID(coClazz, "<init>", "(IIIID[F[F)V");
+ coTypeId = env->GetFieldID(coClazz, "mType", "I");
+ coIdId = env->GetFieldID(coClazz, "mId", "I");
+ coInterpolatorTypeId = env->GetFieldID(coClazz, "mInterpolatorType", "I");
+ coOptionFlagsId = env->GetFieldID(coClazz, "mOptionFlags", "I");
+ coDurationMsId = env->GetFieldID(coClazz, "mDurationMs", "D");
+ coTimesId = env->GetFieldID(coClazz, "mTimes", "[F");
+ coVolumesId = env->GetFieldID(coClazz, "mVolumes", "[F");
+ env->DeleteLocalRef(lclazz);
+
+ lclazz = env->FindClass("android/media/VolumeShaper$Operation");
+ if (lclazz == nullptr) {
+ return;
+ }
+ opClazz = (jclass)env->NewGlobalRef(lclazz);
+ if (opClazz == nullptr) {
+ return;
+ }
+ opConstructId = env->GetMethodID(opClazz, "<init>", "(II)V");
+ opFlagsId = env->GetFieldID(opClazz, "mFlags", "I");
+ opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I");
+ env->DeleteLocalRef(lclazz);
+
+ lclazz = env->FindClass("android/media/VolumeShaper$State");
+ if (lclazz == nullptr) {
+ return;
+ }
+ stClazz = (jclass)env->NewGlobalRef(lclazz);
+ if (stClazz == nullptr) {
+ return;
+ }
+ stConstructId = env->GetMethodID(stClazz, "<init>", "(FF)V");
+ stVolumeId = env->GetFieldID(stClazz, "mVolume", "F");
+ stXOffsetId = env->GetFieldID(stClazz, "mXOffset", "F");
+ env->DeleteLocalRef(lclazz);
+ }
+
+ void exit(JNIEnv *env) {
+ env->DeleteGlobalRef(coClazz);
+ coClazz = nullptr;
+ }
+ };
+
+ static sp<VolumeShaper::Configuration> convertJobjectToConfiguration(
+ JNIEnv *env, const fields_t &fields, jobject jshaper) {
+ sp<VolumeShaper::Configuration> configuration = new VolumeShaper::Configuration();
+
+ configuration->setType(
+ (VolumeShaper::Configuration::Type)env->GetIntField(jshaper, fields.coTypeId));
+ configuration->setId(
+ (int)env->GetIntField(jshaper, fields.coIdId));
+ if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
+ configuration->setInterpolatorType(
+ (VolumeShaper::Configuration::InterpolatorType)
+ env->GetIntField(jshaper, fields.coInterpolatorTypeId));
+ configuration->setOptionFlags(
+ (VolumeShaper::Configuration::OptionFlag)
+ env->GetIntField(jshaper, fields.coOptionFlagsId));
+ configuration->setDurationMs(
+ (double)env->GetDoubleField(jshaper, fields.coDurationMsId));
+
+ // convert point arrays
+ jobject xobj = env->GetObjectField(jshaper, fields.coTimesId);
+ jfloatArray *xarray = reinterpret_cast<jfloatArray*>(&xobj);
+ jsize xlen = env->GetArrayLength(*xarray);
+ /* const */ float * const x =
+ env->GetFloatArrayElements(*xarray, nullptr /* isCopy */);
+ jobject yobj = env->GetObjectField(jshaper, fields.coVolumesId);
+ jfloatArray *yarray = reinterpret_cast<jfloatArray*>(&yobj);
+ jsize ylen = env->GetArrayLength(*yarray);
+ /* const */ float * const y =
+ env->GetFloatArrayElements(*yarray, nullptr /* isCopy */);
+ if (xlen != ylen) {
+ ALOGE("array size must match");
+ return nullptr;
+ }
+ for (jsize i = 0; i < xlen; ++i) {
+ configuration->emplace(x[i], y[i]);
+ }
+ env->ReleaseFloatArrayElements(*xarray, x, JNI_ABORT); // no need to copy back
+ env->ReleaseFloatArrayElements(*yarray, y, JNI_ABORT);
+ }
+ return configuration;
+ }
+
+ static jobject convertVolumeShaperToJobject(
+ JNIEnv *env, const fields_t &fields,
+ const sp<VolumeShaper::Configuration> &configuration) {
+ jfloatArray xarray = nullptr;
+ jfloatArray yarray = nullptr;
+ if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
+ // convert curve arrays
+ jfloatArray xarray = env->NewFloatArray(configuration->size());
+ jfloatArray yarray = env->NewFloatArray(configuration->size());
+ float * const x = env->GetFloatArrayElements(xarray, nullptr /* isCopy */);
+ float * const y = env->GetFloatArrayElements(yarray, nullptr /* isCopy */);
+ float *xptr = x, *yptr = y;
+ for (const auto &pt : *configuration.get()) {
+ *xptr++ = pt.first;
+ *yptr++ = pt.second;
+ }
+ env->ReleaseFloatArrayElements(xarray, x, 0 /* mode */);
+ env->ReleaseFloatArrayElements(yarray, y, 0 /* mode */);
+ }
+
+ // prepare constructor args
+ jvalue args[7];
+ args[0].i = (jint)configuration->getType();
+ args[1].i = (jint)configuration->getId();
+ args[2].i = (jint)configuration->getInterpolatorType();
+ args[3].i = (jint)configuration->getOptionFlags();
+ args[4].d = (jdouble)configuration->getDurationMs();
+ args[5].l = xarray;
+ args[6].l = yarray;
+ jobject jshaper = env->NewObjectA(fields.coClazz, fields.coConstructId, args);
+ return jshaper;
+ }
+
+ static sp<VolumeShaper::Operation> convertJobjectToOperation(
+ JNIEnv *env, const fields_t &fields, jobject joperation) {
+ VolumeShaper::Operation::Flag flags =
+ (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId);
+ int replaceId = env->GetIntField(joperation, fields.opReplaceIdId);
+
+ sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId);
+ return operation;
+ }
+
+ static jobject convertOperationToJobject(
+ JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) {
+ // prepare constructor args
+ jvalue args[2];
+ args[0].i = (jint)operation->getFlags();
+ args[1].i = (jint)operation->getReplaceId();
+
+ jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args);
+ return joperation;
+ }
+
+ static sp<VolumeShaper::State> convertJobjectToState(
+ JNIEnv *env, const fields_t &fields, jobject jstate) {
+ float volume = env->GetFloatField(jstate, fields.stVolumeId);
+ float xOffset = env->GetFloatField(jstate, fields.stXOffsetId);
+
+ sp<VolumeShaper::State> state = new VolumeShaper::State(volume, xOffset);
+ return state;
+ }
+
+ static jobject convertStateToJobject(
+ JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::State> &state) {
+ // prepare constructor args
+ jvalue args[2];
+ args[0].f = (jfloat)state->getVolume();
+ args[1].f = (jfloat)state->getXOffset();
+
+ jobject jstate = env->NewObjectA(fields.stClazz, fields.stConstructId, args);
+ return jstate;
+ }
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_VOLUME_SHAPER_H_
diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java
index acee5dd..3831cf7 100644
--- a/obex/javax/obex/ServerSession.java
+++ b/obex/javax/obex/ServerSession.java
@@ -658,6 +658,11 @@
*/
byte[] sendData = new byte[totalLength];
int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
+ if (maxRxLength > mMaxPacketLength) {
+ if(V) Log.v(TAG,"Set maxRxLength to min of maxRxServrLen:" + maxRxLength +
+ " and MaxNegotiated from Client: " + mMaxPacketLength);
+ maxRxLength = mMaxPacketLength;
+ }
sendData[0] = (byte)code;
sendData[1] = length[2];
sendData[2] = length[3];
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 8a970da..25127ef 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -33,7 +33,6 @@
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
-import android.widget.Toast;
public class DeviceChooserActivity extends Activity {
@@ -129,12 +128,9 @@
}
protected void onPairTapped(BluetoothDevice selectedDevice) {
+ getService().onDeviceSelected();
setResult(RESULT_OK,
new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice));
finish();
}
-
- private void toast(String msg) {
- Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
- }
}
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index ccbee2a..11c722d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -32,16 +32,15 @@
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationRequest;
-import android.companion.BluetoothDeviceFilterUtils;
import android.companion.BluetoothLEDeviceFilter;
-import android.companion.ICompanionDeviceManagerService;
-import android.companion.IOnAssociateCallback;
+import android.companion.ICompanionDeviceDiscoveryService;
+import android.companion.ICompanionDeviceDiscoveryServiceCallback;
+import android.companion.IFindDeviceCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.RemoteException;
@@ -70,21 +69,25 @@
List<BluetoothDevice> mDevicesFound;
BluetoothDevice mSelectedDevice;
DevicesAdapter mDevicesAdapter;
- IOnAssociateCallback mCallback;
+ IFindDeviceCallback mFindCallback;
+ ICompanionDeviceDiscoveryServiceCallback mServiceCallback;
String mCallingPackage;
- private final ICompanionDeviceManagerService mBinder =
- new ICompanionDeviceManagerService.Stub() {
+ private final ICompanionDeviceDiscoveryService mBinder =
+ new ICompanionDeviceDiscoveryService.Stub() {
@Override
public void startDiscovery(AssociationRequest request,
- IOnAssociateCallback callback,
- String callingPackage) throws RemoteException {
+ String callingPackage,
+ IFindDeviceCallback findCallback,
+ ICompanionDeviceDiscoveryServiceCallback serviceCallback) {
if (DEBUG) {
Log.i(LOG_TAG,
- "startDiscovery() called with: filter = [" + request + "], callback = ["
- + callback + "]");
+ "startDiscovery() called with: filter = [" + request
+ + "], findCallback = [" + findCallback + "]"
+ + "], serviceCallback = [" + serviceCallback + "]");
}
- mCallback = callback;
+ mFindCallback = findCallback;
+ mServiceCallback = serviceCallback;
mCallingPackage = callingPackage;
DeviceDiscoveryService.this.startDiscovery(request);
}
@@ -171,6 +174,14 @@
return super.onUnbind(intent);
}
+ public void onDeviceSelected() {
+ try {
+ mServiceCallback.onDeviceSelected(mCallingPackage, getUserId());
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error reporting selected device");
+ }
+ }
+
private void stopScan() {
if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
mBluetoothAdapter.cancelDiscovery();
@@ -205,7 +216,7 @@
//TODO also, on timeout -> call onFailure
private void onReadyToShowUI() {
try {
- mCallback.onSuccess(PendingIntent.getActivity(
+ mFindCallback.onSuccess(PendingIntent.getActivity(
this, 0,
new Intent(this, DeviceChooserActivity.class),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 11e2a71..8653523 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -215,6 +215,13 @@
return colorAccent;
}
+ public static Drawable getDrawable(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ Drawable drawable = ta.getDrawable(0);
+ ta.recycle();
+ return drawable;
+ }
+
/**
* Determine whether a package is a "system package", in which case certain things (like
* disabling notifications or disabling the package altogether) should be disallowed.
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 561d924..1f03b51 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1504,4 +1504,20 @@
return isMusicApp;
}
};
+
+ public static final AppFilter FILTER_OTHER_APPS = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ boolean isCategorized;
+ synchronized(entry) {
+ isCategorized = entry.info.category == ApplicationInfo.CATEGORY_AUDIO ||
+ entry.info.category == ApplicationInfo.CATEGORY_GAME;
+ }
+ return !isCategorized;
+ }
+ };
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 6010621..9ad0b3e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -322,6 +322,7 @@
}
}
+ @Deprecated
public boolean openTile(Tile tile) {
closeDrawer();
if (tile == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 0ec16ae2..9ac4d2d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -510,7 +510,7 @@
}
NetworkKey key = NetworkKey.createFromScanResult(result);
- if (!mRequestedScores.contains(key)) {
+ if (key != null && !mRequestedScores.contains(key)) {
scoresToRequest.add(key);
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 8a6ae86..80e1cbf 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -86,4 +86,25 @@
assertThat(ApplicationsState.FILTER_AUDIO.filterApp(mEntry)).isFalse();
}
+
+ @Test
+ public void testOtherAppsRejectsAudio() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
+
+ assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testOtherAppsRejectsGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testOtherAppsAcceptsDefaultCategory() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+ assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isTrue();
+ }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 499b6ae..136f17e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -227,7 +227,4 @@
<!-- default setting for Settings.System.END_BUTTON_BEHAVIOR : END_BUTTON_BEHAVIOR_SLEEP -->
<integer name="def_end_button_behavior">0x2</integer>
-
- <!--Default settings for network recommendations. -->
- <string name="def_network_recommendations_package" translatable="false">com.android.networkrecommendation</string>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index edcb9b5..d5787e6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3182,24 +3182,7 @@
}
if (currentVersion == 138) {
- // Version 139: Applying the default to NETWORK_RECOMMENDATIONS_PACKAGE
- if (userId == UserHandle.USER_SYSTEM) {
- final SettingsState globalSettings = getGlobalSettingsLocked();
- final String defaultAppPackage = getContext().getResources()
- .getString(R.string.def_network_recommendations_package);
-
- // Set the network recommendations package name
- globalSettings.insertSettingLocked(
- Global.NETWORK_RECOMMENDATIONS_PACKAGE,
- defaultAppPackage, null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
-
- // Clear the scorer setting since it's no longer needed.
- globalSettings.insertSettingLocked(
- Global.NETWORK_SCORER_APP,
- null, null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
- }
+ // Version 139: Removed.
currentVersion = 139;
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d4c7c7a..2e115ab 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -154,8 +154,8 @@
android:name=".BugreportReceiver"
android:permission="android.permission.DUMP">
<intent-filter>
- <action android:name="android.intent.action.BUGREPORT_STARTED" />
- <action android:name="android.intent.action.BUGREPORT_FINISHED" />
+ <action android:name="com.android.internal.intent.action.BUGREPORT_STARTED" />
+ <action android:name="com.android.internal.intent.action.BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
@@ -163,7 +163,7 @@
android:name=".RemoteBugreportReceiver"
android:permission="android.permission.DUMP">
<intent-filter>
- <action android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" />
+ <action android:name="com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 37ea537..12d0c03 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -133,10 +133,12 @@
private static final String AUTHORITY = "com.android.shell";
// External intents sent by dumpstate.
- static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
- static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
+ static final String INTENT_BUGREPORT_STARTED =
+ "com.android.internal.intent.action.BUGREPORT_STARTED";
+ static final String INTENT_BUGREPORT_FINISHED =
+ "com.android.internal.intent.action.BUGREPORT_FINISHED";
static final String INTENT_REMOTE_BUGREPORT_FINISHED =
- "android.intent.action.REMOTE_BUGREPORT_FINISHED";
+ "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
// Internal intents used on notification actions.
static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
@@ -860,15 +862,19 @@
.append(SystemProperties.get("ro.build.description"))
.append("\nSerial number: ")
.append(SystemProperties.get("ro.serialno"));
+ int descriptionLength = 0;
if (!TextUtils.isEmpty(info.description)) {
messageBody.append("\nDescription: ").append(info.description);
+ descriptionLength = info.description.length();
}
intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
+ Log.d(TAG, "share intent: bureportUri=" + bugreportUri);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
for (File screenshot : info.screenshotFiles) {
final Uri screenshotUri = getUri(context, screenshot);
+ Log.d(TAG, "share intent: screenshotUri=" + screenshotUri);
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
attachments.add(screenshotUri);
}
@@ -887,6 +893,10 @@
// default profile.
}
+ // Log what was sent to the intent
+ Log.d(TAG, "share intent: EXTRA_SUBJECT=" + subject + ", EXTRA_TEXT=" + messageBody.length()
+ + " chars, description=" + descriptionLength + " chars");
+
return intent;
}
@@ -1266,14 +1276,17 @@
return;
}
if (title != null && !title.equals(info.title)) {
+ Log.d(TAG, "updating bugreport title: " + title);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
}
info.title = title;
if (description != null && !description.equals(info.description)) {
+ Log.d(TAG, "updating bugreport description: " + description.length() + " chars");
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
}
info.description = description;
if (name != null && !name.equals(info.name)) {
+ Log.d(TAG, "updating bugreport name: " + name);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
info.name = name;
updateProgress(info);
@@ -1684,17 +1697,34 @@
public String toString() {
final float percent = ((float) progress * 100 / max);
final float realPercent = ((float) realProgress * 100 / realMax);
- return "\tid: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished
- + "\n\ttitle: " + title
- + "\n\tdescription: " + description
- + "\n\tfile: " + bugreportFile
- + "\n\tscreenshots: " + screenshotFiles
- + "\n\tprogress: " + progress + "/" + max + " (" + percent + ")"
- + "\n\treal progress: " + realProgress + "/" + realMax + " (" + realPercent
- + ")"
- + "\n\tlast_update: " + getFormattedLastUpdate()
- + "\n\taddingDetailsToZip: " + addingDetailsToZip + " addedDetailsToZip: "
- + addedDetailsToZip;
+
+ final StringBuilder builder = new StringBuilder()
+ .append("\tid: ").append(id)
+ .append(", pid: ").append(pid)
+ .append(", name: ").append(name)
+ .append(", finished: ").append(finished)
+ .append("\n\ttitle: ").append(title)
+ .append("\n\tdescription: ");
+ if (description == null) {
+ builder.append("null");
+ } else {
+ if (TextUtils.getTrimmedLength(description) == 0) {
+ builder.append("empty ");
+ }
+ builder.append("(").append(description.length()).append(" chars)");
+ }
+
+ return builder
+ .append("\n\tfile: ").append(bugreportFile)
+ .append("\n\tscreenshots: ").append(screenshotFiles)
+ .append("\n\tprogress: ").append(progress).append("/").append(max)
+ .append(" (").append(percent).append(")")
+ .append("\n\treal progress: ").append(realProgress).append("/").append(realMax)
+ .append(" (").append(realPercent).append(")")
+ .append("\n\tlast_update: ").append(getFormattedLastUpdate())
+ .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
+ .append(" addedDetailsToZip: ").append(addedDetailsToZip)
+ .toString();
}
// Parcelable contract
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
index 13fc76c..79a0c35 100644
--- a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
@@ -24,7 +24,9 @@
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.annotations.Requires;
+@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
public class SampleOverlayPlugin implements OverlayPlugin {
private static final String TAG = "SampleOverlayPlugin";
private Context mPluginContext;
@@ -36,12 +38,6 @@
private float mStatusBarHeight;
@Override
- public int getVersion() {
- Log.d(TAG, "getVersion " + VERSION);
- return VERSION;
- }
-
- @Override
public void onCreate(Context sysuiContext, Context pluginContext) {
Log.d(TAG, "onCreate");
mPluginContext = pluginContext;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
index 9c173bd..97dbafd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java
@@ -14,6 +14,8 @@
package com.android.systemui.plugins;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -21,6 +23,7 @@
* An Intent Button represents a triggerable element in SysUI that consists of an
* Icon and an intent to trigger when it is activated (clicked, swiped, etc.).
*/
+@ProvidesInterface(version = IntentButtonProvider.VERSION)
public interface IntentButtonProvider extends Plugin {
public static final int VERSION = 1;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
index f5074f7..61aa60b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@@ -13,12 +13,15 @@
*/
package com.android.systemui.plugins;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
import android.view.View;
+@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
public interface OverlayPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
- int VERSION = 1;
+ int VERSION = 2;
void setup(View statusBar, View navBar);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
index e75ecb7..bb93367 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -13,6 +13,8 @@
*/
package com.android.systemui.plugins;
+import com.android.systemui.plugins.annotations.Requires;
+
import android.content.Context;
/**
@@ -111,18 +113,13 @@
public interface Plugin {
/**
- * Should be implemented as the following directly referencing the version constant
- * from the plugin interface being implemented, this will allow recompiles to automatically
- * pick up the current version.
- * <pre class="prettyprint">
- * {@literal
- * public int getVersion() {
- * return VERSION;
- * }
- * }
- * @return
+ * @deprecated
+ * @see Requires
*/
- int getVersion();
+ default int getVersion() {
+ // Default of -1 indicates the plugin supports the new Requires model.
+ return -1;
+ }
default void onCreate(Context sysuiContext, Context pluginContext) {
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java
new file mode 100644
index 0000000..dbbf047
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java
@@ -0,0 +1,27 @@
+/*
+ * 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.plugins.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used for repeated @DependsOn internally, not for plugin
+ * use.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Dependencies {
+ DependsOn[] value();
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java
new file mode 100644
index 0000000..b81d673
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java
@@ -0,0 +1,32 @@
+/*
+ * 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.plugins.annotations;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used to indicate that an interface in the plugin library needs another
+ * interface to function properly. When this is added, it will be enforced
+ * that all plugins that @Requires the annotated interface also @Requires
+ * the specified class as well.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(value = Dependencies.class)
+public @interface DependsOn {
+ Class<?> target();
+
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java
new file mode 100644
index 0000000..d0e14b8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java
@@ -0,0 +1,30 @@
+/*
+ * 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.plugins.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Should be added to all interfaces in plugin lib to specify their
+ * current version and optionally their action to implement the plugin.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ProvidesInterface {
+ int version();
+
+ String action() default "";
+
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java
new file mode 100644
index 0000000..9cfa279
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java
@@ -0,0 +1,27 @@
+/*
+ * 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.plugins.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used for repeated @Requires internally, not for plugin
+ * use.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Requirements {
+ Requires[] value();
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java
new file mode 100644
index 0000000..e1b1303
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java
@@ -0,0 +1,33 @@
+/*
+ * 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.plugins.annotations;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used to annotate which interfaces a given plugin depends on.
+ *
+ * At minimum all plugins should have at least one @Requires annotation
+ * for the plugin interface that they are implementing. They will also
+ * need an @Requires for each class that the plugin interface @DependsOn.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(value = Requirements.class)
+public @interface Requires {
+ Class<?> target();
+ int version();
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java
index 688df46..0688481 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java
@@ -20,10 +20,12 @@
import android.content.Context;
import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
* Provides a {@link DozeUi}.
*/
+@ProvidesInterface(action = DozeProvider.ACTION, version = DozeProvider.VERSION)
public interface DozeProvider extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_DOZE";
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index e21a282..b7467eb 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -14,29 +14,32 @@
package com.android.systemui.plugins.qs;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
+import com.android.systemui.plugins.FragmentBase;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.qs.QS.Callback;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.HeightListener;
+
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.RelativeLayout;
-import com.android.systemui.plugins.FragmentBase;
-
/**
* Fragment that contains QS in the notification shade. Most of the interface is for
* handling the expand/collapsing of the view interaction.
*/
+@ProvidesInterface(action = QS.ACTION, version = QS.VERSION)
+@DependsOn(target = HeightListener.class)
+@DependsOn(target = Callback.class)
+@DependsOn(target = DetailAdapter.class)
public interface QS extends FragmentBase {
public static final String ACTION = "com.android.systemui.action.PLUGIN_QS";
- // This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader
- // change in incompatible ways.
public static final int VERSION = 5;
String TAG = "QS";
@@ -64,17 +67,23 @@
public abstract void setContainer(ViewGroup container);
+ @ProvidesInterface(version = HeightListener.VERSION)
public interface HeightListener {
+ public static final int VERSION = 1;
void onQsHeightChanged();
}
+ @ProvidesInterface(version = Callback.VERSION)
public interface Callback {
+ public static final int VERSION = 1;
void onShowingDetail(DetailAdapter detail, int x, int y);
void onToggleStateChanged(boolean state);
void onScanStateChanged(boolean state);
}
+ @ProvidesInterface(version = DetailAdapter.VERSION)
public interface DetailAdapter {
+ public static final int VERSION = 1;
CharSequence getTitle();
Boolean getToggleState();
default boolean getToggleEnabled() {
@@ -92,7 +101,9 @@
default boolean hasHeader() { return true; }
}
+ @ProvidesInterface(version = BaseStatusBarHeader.VERSION)
public abstract static class BaseStatusBarHeader extends RelativeLayout {
+ public static final int VERSION = 1;
public BaseStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
index f2069b8..529c421 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
@@ -10,7 +10,10 @@
import java.util.ArrayList;
import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+@ProvidesInterface(action = NotificationMenuRowProvider.ACTION,
+ version = NotificationMenuRowProvider.VERSION)
public interface NotificationMenuRowProvider extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
index d54e33f..5243228 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -14,14 +14,15 @@
package com.android.systemui.plugins.statusbar.phone;
-import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+@ProvidesInterface(action = NavBarButtonProvider.ACTION, version = NavBarButtonProvider.VERSION)
public interface NavBarButtonProvider extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON";
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 918d6e9..ddee89e 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
@@ -17,7 +17,9 @@
import android.view.MotionEvent;
import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+@ProvidesInterface(action = NavGesture.ACTION, version = NavBarButtonProvider.VERSION)
public interface NavGesture extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_GESTURE";
diff --git a/packages/SystemUI/res/drawable/ic_data_saver.xml b/packages/SystemUI/res/drawable/ic_data_saver.xml
index 9c3bd3a..64bbff0 100644
--- a/packages/SystemUI/res/drawable/ic_data_saver.xml
+++ b/packages/SystemUI/res/drawable/ic_data_saver.xml
@@ -23,7 +23,7 @@
android:fillColor="#FFFFFFFF"
android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/>
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#54FFFFFF"
android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/>
<path
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_data_saver_off.xml b/packages/SystemUI/res/drawable/ic_data_saver_off.xml
index 918c61c..3001ad9 100644
--- a/packages/SystemUI/res/drawable/ic_data_saver_off.xml
+++ b/packages/SystemUI/res/drawable/ic_data_saver_off.xml
@@ -20,9 +20,9 @@
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/>
<path
- android:fillColor="#4DFFFFFF"
+ android:fillColor="#FFFFFFFF"
android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_remove_circle.xml b/packages/SystemUI/res/drawable/ic_remove_circle.xml
new file mode 100644
index 0000000..439cc78
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_remove_circle.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="48dp"
+ android:width="48dp"
+ android:tint="#db4437"
+ android:viewportHeight="48"
+ android:viewportWidth="48" >
+ <path android:fillColor="@android:color/white"
+ android:pathData="M24,4C12.95,4,4,12.95,4,24
+ s8.95,20,20,20,20-8.95,20-20
+ S35.05,4,24,4zm10,22H14v-4h20v4z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
new file mode 100644
index 0000000..acae9f5
--- /dev/null
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+
+<!-- Loaded into BatteryMeterView as necessary -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/battery_percentage_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textColor="?android:attr/textColorPrimary"
+ android:gravity="center_vertical|start"
+ android:paddingStart="4dp"
+ />
diff --git a/packages/SystemUI/res/layout/divider.xml b/packages/SystemUI/res/layout/divider.xml
index 9581437..f1f0df0 100644
--- a/packages/SystemUI/res/layout/divider.xml
+++ b/packages/SystemUI/res/layout/divider.xml
@@ -16,5 +16,6 @@
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="72dp"
android:layout_height="1dp"
+ android:layout_marginTop="8dp"
android:background="?android:attr/colorForeground"
android:alpha="?android:attr/disabledAlpha" />
diff --git a/packages/SystemUI/res/layout/preference_widget_radiobutton.xml b/packages/SystemUI/res/layout/preference_widget_radiobutton.xml
new file mode 100644
index 0000000..b3ec43d
--- /dev/null
+++ b/packages/SystemUI/res/layout/preference_widget_radiobutton.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 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.
+-->
+
+<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
+ inside android.R.layout.preference. -->
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:focusable="false"
+ android:clickable="false" />
diff --git a/packages/SystemUI/res/layout/qs_page_indicator.xml b/packages/SystemUI/res/layout/qs_page_indicator.xml
index 02bd31a..583753a 100644
--- a/packages/SystemUI/res/layout/qs_page_indicator.xml
+++ b/packages/SystemUI/res/layout/qs_page_indicator.xml
@@ -20,9 +20,8 @@
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
- android:layout_marginTop="40dp"
android:layout_marginBottom="24dp"
android:focusable="true"
android:gravity="center"
android:importantForAccessibility="yes"
- android:visibility="gone"/>
\ No newline at end of file
+ android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index 8ff1d1e..00427cb 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -19,6 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingBottom="24dp"
android:clipChildren="false"
android:clipToPadding="false">
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index a093b87..8d1f9e4 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -16,10 +16,12 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingTop="16dp">
+ android:gravity="center_horizontal"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
<TextView android:id="@+id/tile_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index 6988c76..080f553 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -17,7 +17,6 @@
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="48dp"
android:layout_width="match_parent"
- android:layout_marginBottom="24dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
style="@style/BrightnessDialogContainer">
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 3e8e72a..78d4bdd 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -85,16 +85,6 @@
<include layout="@layout/system_icons" />
</FrameLayout>
-
- <TextView
- android:id="@+id/battery_level"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="@dimen/header_battery_margin_expanded"
- android:importantForAccessibility="noHideDescendants"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/battery_level_text_size"/>
</LinearLayout>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 3a33992..bfa92ad 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -31,9 +31,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
- <!-- battery must be padded below to match assets -->
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
- android:layout_height="@dimen/status_bar_battery_icon_height"
- android:layout_width="@dimen/status_bar_battery_icon_width"
- android:layout_marginBottom="@dimen/battery_margin_bottom"/>
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e737d2d..40d4d6f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -69,8 +69,8 @@
<!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">92dp</dimen>
- <!-- Height of a small notification in the status bar if it is a large (like messaging)-->
- <dimen name="notification_min_height_large">132dp</dimen>
+ <!-- Increased height of a small notification in the status bar -->
+ <dimen name="notification_min_height_increased">132dp</dimen>
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
@@ -87,6 +87,9 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height">148dp</dimen>
+ <!-- Height of a heads up notification in the status bar -->
+ <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
+
<!-- a threshold in dp per second that is considered fast scrolling -->
<dimen name="scroll_fast_threshold">1500dp</dimen>
@@ -216,8 +219,8 @@
<!-- The size of the gesture span needed to activate the "pull" notification expansion -->
<dimen name="pull_span_min">25dp</dimen>
- <dimen name="qs_tile_height">80dp</dimen>
- <dimen name="qs_tile_margin">36dp</dimen>
+ <dimen name="qs_tile_height">88dp</dimen>
+ <dimen name="qs_tile_margin">28dp</dimen>
<dimen name="qs_tile_margin_top">16dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 67def4f..b825cfb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1527,14 +1527,12 @@
<!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
<string name="nav_bar_layout">Layout</string>
- <!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] -->
- <string name="nav_bar_left">Left</string>
-
- <!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] -->
- <string name="nav_bar_right">Right</string>
+ <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
+ <string name="left_nav_bar_button_type">Extra left button type</string>
<!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
- <string name="nav_bar_button_type">Button type</string>
+ <string name="right_nav_bar_button_type">Extra right button type</string>
+
<!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] -->
<string name="nav_bar_default"> (default)</string>
@@ -1543,7 +1541,7 @@
<string-array name="nav_bar_buttons">
<item>Clipboard</item>
<item>Keycode</item>
- <item>Menu / Keyboard Switcher</item>
+ <item>Keyboard switcher</item>
<item>None</item>
</string-array>
<string-array name="nav_bar_button_values" translatable="false">
@@ -1555,10 +1553,10 @@
<!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] -->
<string-array name="nav_bar_layouts">
- <item>Divided (default)</item>
- <item>Centered</item>
- <item>Left-aligned</item>
- <item>Right-aligned</item>
+ <item>Normal</item>
+ <item>Compact</item>
+ <item>Left-leaning</item>
+ <item>Right-leaning</item>
</string-array>
<string-array name="nav_bar_layouts_values" translatable="false">
@@ -1569,7 +1567,7 @@
</string-array>
<!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] -->
- <string name="menu_ime">Menu / Keyboard Switcher</string>
+ <string name="menu_ime">Keyboard switcher</string>
<!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] -->
<string name="save">Save</string>
<!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] -->
@@ -1585,10 +1583,16 @@
<string name="accessibility_key">Custom navigation button</string>
<!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
- <string name="keycode">Keycode</string>
+ <string name="left_keycode">Left keycode</string>
+
+ <!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
+ <string name="right_keycode">Right keycode</string>
<!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
- <string name="icon">Icon</string>
+ <string name="left_icon">Left icon</string>
+
+ <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
+ <string name="right_icon">Right icon</string>
<!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
<string name="drag_to_add_tiles">Drag to add tiles</string>
@@ -1721,10 +1725,17 @@
not appear on production builds ever. -->
<string name="tuner_doze" translatable="false">Ambient Display</string>
+ <!-- Ambient display, Sensors wake up device of the tuner. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="tuner_doze_sensors_wake_up_fully" translatable="false">Wake up device on double tap or lift</string>
+
<!-- Ambient display always-on of the tuner. Non-translatable since it should
not appear on production builds ever. -->
<string name="tuner_doze_always_on" translatable="false">Always on</string>
+ <!-- SysUI Tuner: Section to customize lockscreen shortcuts [CHAR LIMIT=60] -->
+ <string name="tuner_lock_screen">Lock screen</string>
+
<!-- Making the PIP fullscreen [CHAR LIMIT=25] -->
<string name="pip_phone_expand">Expand</string>
@@ -1734,18 +1745,6 @@
<!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
- <!-- PIP section of the tuner. Non-translatable since it should
- not appear on production builds ever. -->
- <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
-
- <!-- PIP drag to dismiss title. Non-translatable since it should
- not appear on production builds ever. -->
- <string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string>
-
- <!-- PIP drag to dismiss description. Non-translatable since it should
- not appear on production builds ever. -->
- <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
-
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
@@ -1760,20 +1759,47 @@
<!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
- <!-- SysUI Tuner: Group of settings for left lock screen affordance [CHAR LIMIT=60] -->
- <string name="lockscreen_left">Left</string>
-
- <!-- SysUI Tuner: Group of settings for right lock screen affordance [CHAR LIMIT=60] -->
- <string name="lockscreen_right">Right</string>
-
- <!-- SysUI Tuner: Switch controlling whether to customize lock screen button [CHAR LIMIT=60] -->
- <string name="lockscreen_customize">Customize shortcut</string>
+ <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
+ <string name="lockscreen_shortcut_left">Left shortcut</string>
<!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
- <string name="lockscreen_shortcut">Shortcut</string>
+ <string name="lockscreen_shortcut_right">Right shortcut</string>
- <!-- SysUI Tuner: Switch to control if device gets unlocked [CHAR LIMIT=60] -->
- <string name="lockscreen_unlock">Prompt for password</string>
+ <!-- SysUI Tuner: Switch to control if device gets unlocked by left shortcut [CHAR LIMIT=60] -->
+ <string name="lockscreen_unlock_left">Left shortcut also unlocks</string>
+
+ <!-- SysUI Tuner: Switch to control if device gets unlocked by right shortcut [CHAR LIMIT=60] -->
+ <string name="lockscreen_unlock_right">Right shortcut also unlocks</string>
+
+ <!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
+ <string name="lockscreen_none">None</string>
+
+ <!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
+ <string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
+
+ <!-- SysUI Tuner: Label for section of other apps that can be launched [CHAR LIMIT=60] -->
+ <string name="tuner_other_apps">Other apps</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a circle [CHAR LIMIT=60] -->
+ <string name="tuner_circle">Circle</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a plus [CHAR LIMIT=60] -->
+ <string name="tuner_plus">Plus</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a minus [CHAR LIMIT=60] -->
+ <string name="tuner_minus">Minus</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a left [CHAR LIMIT=60] -->
+ <string name="tuner_left">Left</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a right [CHAR LIMIT=60] -->
+ <string name="tuner_right">Right</string>
+
+ <!-- SysUI Tuner: Label for icon shaped like a menu [CHAR LIMIT=60] -->
+ <string name="tuner_menu">Menu</string>
+
+ <!-- SysUI Tuner: App subheading for shortcut selection [CHAR LIMIT=60] -->
+ <string name="tuner_app"><xliff:g id="app">%1$s</xliff:g> app</string>
<!-- Title for the notification channel containing important alerts like low battery. [CHAR LIMIT=NONE] -->
<string name="notification_channel_alerts">Alerts</string>
diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml
index 73e29af..1e7d266 100644
--- a/packages/SystemUI/res/xml/lockscreen_settings.xml
+++ b/packages/SystemUI/res/xml/lockscreen_settings.xml
@@ -18,42 +18,25 @@
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:title="@string/other">
- <PreferenceCategory
- android:key="left"
- android:title="@string/lockscreen_left">
- <SwitchPreference
- android:key="customize"
- android:title="@string/lockscreen_customize" />
+ <Preference
+ android:key="sysui_keyguard_left"
+ android:title="@string/lockscreen_shortcut_left"
+ android:fragment="com.android.systemui.tuner.ShortcutPicker" />
- <Preference
- android:key="shortcut"
- android:title="@string/lockscreen_shortcut" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_left_unlock"
+ android:title="@string/lockscreen_unlock_left"
+ sysui:defValue="true" />
- <com.android.systemui.tuner.TunerSwitch
- android:key="sysui_keyguard_left_unlock"
- android:title="@string/lockscreen_unlock"
- sysui:defValue="true" />
+ <Preference
+ android:key="sysui_keyguard_right"
+ android:title="@string/lockscreen_shortcut_right"
+ android:fragment="com.android.systemui.tuner.ShortcutPicker" />
- </PreferenceCategory>
-
- <PreferenceCategory
- android:key="right"
- android:title="@string/lockscreen_right">
-
- <SwitchPreference
- android:key="customize"
- android:title="@string/lockscreen_customize" />
-
- <Preference
- android:key="shortcut"
- android:title="@string/lockscreen_shortcut" />
-
- <com.android.systemui.tuner.TunerSwitch
- android:key="sysui_keyguard_right_unlock"
- android:title="@string/lockscreen_unlock"
- sysui:defValue="true" />
-
- </PreferenceCategory>
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_right_unlock"
+ android:title="@string/lockscreen_unlock_right"
+ sysui:defValue="true" />
</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/nav_bar_tuner.xml b/packages/SystemUI/res/xml/nav_bar_tuner.xml
index 6fa8bec..68e8fad 100644
--- a/packages/SystemUI/res/xml/nav_bar_tuner.xml
+++ b/packages/SystemUI/res/xml/nav_bar_tuner.xml
@@ -18,7 +18,7 @@
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:title="@string/nav_bar">
- <ListPreference
+ <com.android.systemui.tuner.RadioListPreference
android:key="layout"
android:title="@string/nav_bar_layout"
android:summary="%s"
@@ -26,54 +26,42 @@
android:entries="@array/nav_bar_layouts"
android:entryValues="@array/nav_bar_layouts_values" />
- <PreferenceCategory
- android:key="left"
- android:title="@string/nav_bar_left">
+ <com.android.systemui.tuner.RadioListPreference
+ android:key="type_left"
+ android:title="@string/left_nav_bar_button_type"
+ android:persistent="false"
+ android:summary="%s"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
- <DropDownPreference
- android:key="type_left"
- android:title="@string/nav_bar_button_type"
- android:persistent="false"
- android:summary="%s"
- android:entries="@array/nav_bar_buttons"
- android:entryValues="@array/nav_bar_button_values" />
+ <Preference
+ android:key="keycode_left"
+ android:persistent="false"
+ android:title="@string/left_keycode" />
- <Preference
- android:key="keycode_left"
- android:persistent="false"
- android:title="@string/keycode" />
+ <com.android.systemui.tuner.RadioListPreference
+ android:key="icon_left"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/left_icon" />
- <com.android.systemui.tuner.BetterListPreference
- android:key="icon_left"
- android:persistent="false"
- android:summary="%s"
- android:title="@string/icon" />
+ <com.android.systemui.tuner.RadioListPreference
+ android:key="type_right"
+ android:title="@string/right_nav_bar_button_type"
+ android:summary="%s"
+ android:persistent="false"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
- </PreferenceCategory>
+ <Preference
+ android:key="keycode_right"
+ android:persistent="false"
+ android:title="@string/right_keycode" />
- <PreferenceCategory
- android:key="right"
- android:title="@string/nav_bar_right">
-
- <DropDownPreference
- android:key="type_right"
- android:title="@string/nav_bar_button_type"
- android:summary="%s"
- android:persistent="false"
- android:entries="@array/nav_bar_buttons"
- android:entryValues="@array/nav_bar_button_values" />
-
- <Preference
- android:key="keycode_right"
- android:persistent="false"
- android:title="@string/keycode" />
-
- <com.android.systemui.tuner.BetterListPreference
- android:key="icon_right"
- android:persistent="false"
- android:summary="%s"
- android:title="@string/icon" />
-
- </PreferenceCategory>
+ <com.android.systemui.tuner.RadioListPreference
+ android:key="icon_right"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/right_icon" />
</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 6198ab7..85f12b5 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -121,18 +121,7 @@
</PreferenceScreen>
- <PreferenceScreen
- android:key="picture_in_picture"
- android:title="@string/picture_in_picture">
-
- <com.android.systemui.tuner.TunerSwitch
- android:key="pip_drag_to_dismiss"
- android:title="@string/pip_drag_to_dismiss_title"
- android:summary="@string/pip_drag_to_dismiss_summary"
- sysui:defValue="false" />
-
- </PreferenceScreen>
-
+ <!--
<PreferenceScreen
android:key="doze"
android:title="@string/tuner_doze">
@@ -142,7 +131,13 @@
android:title="@string/tuner_doze_always_on"
sysui:defValue="false" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="doze_sensors_wake_up_fully"
+ android:title="@string/tuner_doze_sensors_wake_up_fully"
+ sysui:defValue="false" />
+
</PreferenceScreen>
+ -->
<Preference
android:key="nav_bar"
@@ -151,15 +146,10 @@
<Preference
android:key="lockscreen"
- android:title="@string/accessibility_desc_lock_screen"
+ android:title="@string/tuner_lock_screen"
android:fragment="com.android.systemui.tuner.LockscreenFragment" />
<Preference
- android:key="other"
- android:title="@string/other"
- android:fragment="com.android.systemui.tuner.OtherPrefs" />
-
- <Preference
android:key="plugins"
android:title="@string/plugins"
android:fragment="com.android.systemui.tuner.PluginFragment" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 80d4a26..1f58d4c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -595,6 +595,10 @@
}
}
+ public boolean isScreenOn() {
+ return mScreenOn;
+ }
+
static class DisplayClientState {
public int clientGeneration;
public boolean clearing;
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
deleted file mode 100644
index 9068079..0000000
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings;
-import com.android.settingslib.graph.BatteryMeterDrawableBase;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-public class BatteryMeterDrawable extends BatteryMeterDrawableBase implements
- BatteryController.BatteryStateChangeCallback {
-
- public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
-
- private BatteryController mBatteryController;
- private SettingObserver mSettingObserver;
-
- public BatteryMeterDrawable(Context context, int frameColor) {
- super(context, frameColor);
-
- mSettingObserver = new SettingObserver(new Handler(mContext.getMainLooper()));
- }
-
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- setBatteryLevel(level);
- setPluggedIn(pluggedIn);
- }
-
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- setPowerSave(isPowerSave);
- }
-
- public void startListening() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
- updateShowPercent();
- mBatteryController.addCallback(this);
- }
-
- public void stopListening() {
- mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
- mBatteryController.removeCallback(this);
- }
-
- protected void updateShowPercent() {
- setShowPercent(0 != Settings.System.getInt(mContext.getContentResolver(),
- SHOW_PERCENT_SETTING, 0));
- }
-
- public void setBatteryController(BatteryController batteryController) {
- mBatteryController = batteryController;
- setPowerSave(mBatteryController.isPowerSave());
- }
-
- private final class SettingObserver extends ContentObserver {
- public SettingObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- updateShowPercent();
- postInvalidate();
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 69e3874..bda4c95 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -23,10 +23,22 @@
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.settingslib.graph.BatteryMeterDrawableBase;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -37,12 +49,23 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-public class BatteryMeterView extends ImageView implements
+import java.text.NumberFormat;
+
+public class BatteryMeterView extends LinearLayout implements
BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
- private final BatteryMeterDrawable mDrawable;
+ public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
+
+ private final BatteryMeterDrawableBase mDrawable;
private final String mSlotBattery;
+ private final ImageView mBatteryIconView;
+ private TextView mBatteryPercentView;
+
private BatteryController mBatteryController;
+ private SettingObserver mSettingObserver;
+ private int mTextColor;
+ private int mLevel;
+ private boolean mForceShowPercent;
public BatteryMeterView(Context context) {
this(context, null, 0);
@@ -55,16 +78,40 @@
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
defStyle, 0);
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.batterymeter_frame_color));
- mDrawable = new BatteryMeterDrawable(context, frameColor);
+ mDrawable = new BatteryMeterDrawableBase(context, frameColor);
atts.recycle();
+ mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
+
mSlotBattery = context.getString(
com.android.internal.R.string.status_bar_battery);
- setImageDrawable(mDrawable);
+ mBatteryIconView = new ImageView(context);
+ mBatteryIconView.setImageDrawable(mDrawable);
+ final MarginLayoutParams mlp = new MarginLayoutParams(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
+ mlp.setMargins(0, 0, 0,
+ getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
+ addView(mBatteryIconView, mlp);
+
+ updateShowPercent();
+ }
+
+ public void forceShowPercent() {
+ mForceShowPercent = true;
+ updateShowPercent();
+ }
+
+ // StatusBarIconController reaches in here and adjusts the layout parameters of the icon
+ public ImageView getBatteryIconView() {
+ return mBatteryIconView;
}
@Override
@@ -84,9 +131,10 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
mBatteryController = Dependency.get(BatteryController.class);
- mDrawable.setBatteryController(mBatteryController);
mBatteryController.addCallback(this);
- mDrawable.startListening();
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
+ updateShowPercent();
Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
Dependency.get(ConfigurationController.class).addCallback(this);
}
@@ -95,13 +143,17 @@
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBatteryController.removeCallback(this);
- mDrawable.stopListening();
+ getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
Dependency.get(TunerService.class).removeTunable(this);
Dependency.get(ConfigurationController.class).removeCallback(this);
}
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ mDrawable.setBatteryLevel(level);
+ mDrawable.setPluggedIn(pluggedIn);
+ mLevel = level;
+ updatePercentText();
setContentDescription(
getContext().getString(charging ? R.string.accessibility_battery_level_charging
: R.string.accessibility_battery_level, level));
@@ -109,7 +161,40 @@
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
+ mDrawable.setPowerSave(isPowerSave);
+ }
+ private TextView loadPercentView() {
+ return (TextView) LayoutInflater.from(getContext())
+ .inflate(R.layout.battery_percentage_view, null);
+ }
+
+ private void updatePercentText() {
+ if (mBatteryPercentView != null) {
+ mBatteryPercentView.setText(
+ NumberFormat.getPercentInstance().format(mLevel/100f));
+ }
+ }
+
+ private void updateShowPercent() {
+ final boolean showing = mBatteryPercentView != null;
+ if (0 != Settings.System.getInt(getContext().getContentResolver(),
+ BatteryMeterView.SHOW_PERCENT_SETTING, 0) || mForceShowPercent) {
+ if (!showing) {
+ mBatteryPercentView = loadPercentView();
+ if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ updatePercentText();
+ addView(mBatteryPercentView,
+ new ViewGroup.LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT));
+ }
+ } else {
+ if (showing) {
+ removeView(mBatteryPercentView);
+ mBatteryPercentView = null;
+ }
+ }
}
@Override
@@ -133,17 +218,37 @@
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
(int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
- scaledLayoutParams.setMarginsRelative(0, 0, 0, marginBottom);
+ scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
- setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.setLayoutParams(scaledLayoutParams);
}
@Override
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
mDrawable.setDarkIntensity(DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0);
+ setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+ }
+
+ public void setTextColor(int color) {
+ mTextColor = color;
+ if (mBatteryPercentView != null) {
+ mBatteryPercentView.setTextColor(color);
+ }
}
public void setRawColors(int fgColor, int bgColor) {
mDrawable.setColors(fgColor, bgColor);
}
+
+ private final class SettingObserver extends ContentObserver {
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ updateShowPercent();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 273b5e3..f1e7d53 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -25,6 +25,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
@@ -74,6 +76,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.function.Consumer;
/**
* Class to handle ugly dependencies throughout sysui until we determine the
@@ -227,6 +230,9 @@
mProviders.put(StatusBarIconController.class, () ->
new StatusBarIconControllerImpl(mContext));
+ mProviders.put(FragmentService.class, () ->
+ new FragmentService(mContext));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
@@ -282,17 +288,42 @@
T createDependency();
}
+ private <T> void destroyDependency(Class<T> cls, Consumer<T> destroy) {
+ T dep = (T) mDependencies.remove(cls);
+ if (dep != null && destroy != null) {
+ destroy.accept(dep);
+ }
+ }
+
/**
* Used in separate processes (like tuner settings) to init the dependencies.
*/
public static void initDependencies(Context context) {
if (sDependency != null) return;
Dependency d = new Dependency();
- d.mContext = context.getApplicationContext();
+ d.mContext = context;
d.mComponents = new HashMap<>();
d.start();
}
+ /**
+ * Used in separate process teardown to ensure the context isn't leaked.
+ *
+ * TODO: Remove once PreferenceFragment doesn't reference getActivity()
+ * anymore and these context hacks are no longer needed.
+ */
+ public static void clearDependencies() {
+ sDependency = null;
+ }
+
+ /**
+ * Checks to see if a dependency is instantiated, if it is it removes it from
+ * the cache and calls the destroy callback.
+ */
+ public static <T> void destroy(Class<T> cls, Consumer<T> destroy) {
+ sDependency.destroyDependency(cls, destroy);
+ }
+
public static <T> T get(Class<T> cls) {
return sDependency.getDependency(cls);
}
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index 9cc6613..ddd4833 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -53,8 +53,7 @@
private static final String TAG = "PluginInflateContainer";
- private String mAction;
- private int mVersion;
+ private Class<?> mClass;
private View mPluginView;
public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) {
@@ -62,28 +61,25 @@
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer);
String viewType = a.getString(R.styleable.PluginInflateContainer_viewType);
try {
- Class c = Class.forName(viewType);
- mAction = (String) c.getDeclaredField("ACTION").get(null);
- mVersion = (int) c.getDeclaredField("VERSION").get(null);
+ mClass = Class.forName(viewType);
} catch (Exception e) {
Log.d(TAG, "Problem getting class info " + viewType, e);
- mAction = null;
- mVersion = 0;
+ mClass = null;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mAction != null) {
- Dependency.get(PluginManager.class).addPluginListener(mAction, this, mVersion);
+ if (mClass != null) {
+ Dependency.get(PluginManager.class).addPluginListener(this, mClass);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mAction != null) {
+ if (mClass != null) {
Dependency.get(PluginManager.class).removePluginListener(this);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 19ae295..b9ae585 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -49,6 +49,7 @@
Key.QS_WORK_ADDED,
})
public @interface Key {
+ @Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
String DEBUG_MODE_ENABLED = "debugModeEnabled";
String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 187b557..be69867 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -36,6 +36,7 @@
import com.android.systemui.media.RingtonePlayer;
import com.android.systemui.pip.PipUI;
import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.power.PowerUI;
@@ -67,7 +68,6 @@
*/
private final Class<?>[] SERVICES = new Class[] {
Dependency.class,
- FragmentService.class,
NotificationChannels.class,
CommandQueue.CommandQueueStart.class,
KeyguardViewMediator.class,
@@ -207,7 +207,7 @@
mServices[i].onBootCompleted();
}
}
- Dependency.get(PluginManager.class).addPluginListener(OverlayPlugin.ACTION,
+ Dependency.get(PluginManager.class).addPluginListener(
new PluginListener<OverlayPlugin>() {
private ArraySet<OverlayPlugin> mOverlays;
@@ -236,7 +236,7 @@
Dependency.get(StatusBarWindowManager.class).setForcePluginOpen(
mOverlays.size() != 0);
}
- }, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
+ }, OverlayPlugin.class, true /* Allow multiple plugins */);
mServicesStarted = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 7c15096..b3a0eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -91,8 +91,7 @@
@Override
public void requestState(DozeProvider.DozeState state) {
if (state == DozeProvider.DozeState.WAKE_UP) {
- PowerManager pm = context.getSystemService(PowerManager.class);
- pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE");
+ machine.wakeUp();
return;
}
machine.requestState(implState(state));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 6a868d5..c9eb790 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -158,6 +158,11 @@
return mState;
}
+ /** Requests the PowerManager to wake up now. */
+ public void wakeUp() {
+ mDozeService.requestWakeUp();
+ }
+
private boolean isExecutingTransition() {
return !mQueuedRequests.isEmpty();
}
@@ -300,5 +305,8 @@
/** Request a display state. See {@link android.view.Display#STATE_DOZE}. */
void setDozeScreenState(int state);
+
+ /** Request waking up. */
+ void requestWakeUp();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 94dc9a3..e55a597 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import android.os.PowerManager;
+import android.os.SystemClock;
import android.service.dreams.DreamService;
import android.util.Log;
@@ -49,7 +51,7 @@
}
DozeProvider provider = Dependency.get(PluginManager.class)
- .getOneShotPlugin(DozeProvider.ACTION, DozeProvider.VERSION);
+ .getOneShotPlugin(DozeProvider.class);
mDozeMachine = new DozeFactory(provider).assembleMachine(this);
}
@@ -72,4 +74,10 @@
mDozeMachine.dump(pw);
}
}
+
+ @Override
+ public void requestWakeUp() {
+ PowerManager pm = getSystemService(PowerManager.class);
+ pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE");
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index db5a392..b5c7dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -97,7 +97,11 @@
}
private void onSensor(int pulseReason, boolean sensorPerformedProxCheck) {
- requestPulse(pulseReason, sensorPerformedProxCheck);
+ if (mDozeParameters.getSensorsWakeUpFully()) {
+ mMachine.wakeUp();
+ } else {
+ requestPulse(pulseReason, sensorPerformedProxCheck);
+ }
if (pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP) {
final long timeSinceNotification =
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 0c6bf52..57c75bf 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -32,6 +32,7 @@
import android.view.View;
import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginManager;
@@ -171,6 +172,10 @@
return mPlugins;
}
+ void destroy() {
+ mFragments.dispatchDestroy();
+ }
+
public interface FragmentListener {
void onFragmentViewCreated(String tag, Fragment fragment);
@@ -182,8 +187,7 @@
public static FragmentHostManager get(View view) {
try {
- return ((SystemUIApplication) view.getContext().getApplicationContext())
- .getComponent(FragmentService.class).getFragmentHostManager(view);
+ return Dependency.get(FragmentService.class).getFragmentHostManager(view);
} catch (ClassCastException e) {
// TODO: Some auto handling here?
throw e;
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index 85cde10..9a8512d 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -14,6 +14,7 @@
package com.android.systemui.fragments;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -21,6 +22,7 @@
import android.util.Log;
import android.view.View;
+import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIApplication;
@@ -28,16 +30,16 @@
* Holds a map of root views to FragmentHostStates and generates them as needed.
* Also dispatches the configuration changes to all current FragmentHostStates.
*/
-public class FragmentService extends SystemUI {
+public class FragmentService implements ConfigurationChangedReceiver {
private static final String TAG = "FragmentService";
private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>();
private final Handler mHandler = new Handler();
+ private final Context mContext;
- @Override
- public void start() {
- putComponent(FragmentService.class, this);
+ public FragmentService(Context context) {
+ mContext = context;
}
public FragmentHostManager getFragmentHostManager(View view) {
@@ -50,8 +52,14 @@
return state.getFragmentHostManager();
}
+ public void destroyAll() {
+ for (FragmentHostState state : mHosts.values()) {
+ state.mFragmentHostManager.destroy();
+ }
+ }
+
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
for (FragmentHostState state : mHosts.values()) {
state.sendConfigurationChange(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
index 1eaca6f..03bb73d 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -44,8 +44,9 @@
mDefaultClass = defaultFragment;
}
- public void startListening(String action, int version) {
- mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
+ public void startListening() {
+ mPluginManager.addPluginListener(this, mExpectedInterface,
+ false /* Only allow one */);
}
public void stopListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 23eaed9..32b5862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
-import static android.app.ActivityManager.StackId;
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
@@ -32,13 +31,14 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Bouncer between work activities and the activity used to confirm credentials before unlocking
* a managed profile.
@@ -51,52 +51,39 @@
private static final String TAG = "WorkLockActivity";
/**
- * ID of the locked user that this activity blocks access to.
+ * Contains a {@link TaskDescription} for the activity being covered.
*/
- @UserIdInt
- private int mUserId;
-
+ static final String EXTRA_TASK_DESCRIPTION =
+ "com.android.systemui.keyguard.extra.TASK_DESCRIPTION";
+
/**
- * {@see KeyguardManager}
+ * Cached keyguard manager instance populated by {@link #getKeyguardManager}.
+ * @see KeyguardManager
*/
private KeyguardManager mKgm;
- /**
- * {@see DevicePolicyManager}
- */
- private DevicePolicyManager mDpm;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
- mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
- mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
-
- final IntentFilter lockFilter = new IntentFilter();
- lockFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
- registerReceiverAsUser(mLockEventReceiver, UserHandle.ALL, lockFilter,
- /* permission */ null, /* scheduler */ null);
+ registerReceiverAsUser(mLockEventReceiver, UserHandle.ALL,
+ new IntentFilter(Intent.ACTION_DEVICE_LOCKED_CHANGED), /* permission */ null,
+ /* scheduler */ null);
// Once the receiver is registered, check whether anything happened between now and the time
// when this activity was launched. If it did and the user is unlocked now, just quit.
- if (!mKgm.isDeviceLocked(mUserId)) {
+ if (!getKeyguardManager().isDeviceLocked(getTargetUserId())) {
finish();
return;
}
- // Get the organization color; this is a 24-bit integer provided by a DPC, guaranteed to
- // be completely opaque.
- final @ColorInt int color = mDpm.getOrganizationColorForUser(mUserId);
-
// Draw captions overlaid on the content view, so the whole window is one solid color.
setOverlayWithDecorCaptionEnabled(true);
// Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
// redaction switched on.
final View blankView = new View(this);
- blankView.setBackgroundColor(color);
+ blankView.setBackgroundColor(getPrimaryColor());
setContentView(blankView);
}
@@ -127,26 +114,28 @@
@Override
public void setTaskDescription(TaskDescription taskDescription) {
- // Use the previous activity's task description.
+ // Leave unset so we use the previous activity's task description.
}
private final BroadcastReceiver mLockEventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, mUserId);
- if (userId == mUserId && !mKgm.isDeviceLocked(mUserId)) {
+ final int targetUserId = getTargetUserId();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, targetUserId);
+ if (userId == targetUserId && !getKeyguardManager().isDeviceLocked(targetUserId)) {
finish();
}
}
};
private void showConfirmCredentialActivity() {
- if (isFinishing() || !mKgm.isDeviceLocked(mUserId)) {
+ if (isFinishing() || !getKeyguardManager().isDeviceLocked(getTargetUserId())) {
// Don't show the confirm credentials screen if we are already unlocked / unlocking.
return;
}
- final Intent credential = mKgm.createConfirmDeviceCredentialIntent(null, null, mUserId);
+ final Intent credential = getKeyguardManager()
+ .createConfirmDeviceCredentialIntent(null, null, getTargetUserId());
if (credential == null) {
return;
}
@@ -181,4 +170,32 @@
final View view = getWindow().getDecorView();
return ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
}
+
+ private KeyguardManager getKeyguardManager() {
+ if (mKgm == null) {
+ mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+ }
+ return mKgm;
+ }
+
+ @VisibleForTesting
+ @UserIdInt
+ final int getTargetUserId() {
+ return getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ @ColorInt
+ final int getPrimaryColor() {
+ final TaskDescription taskDescription = (TaskDescription)
+ getIntent().getExtra(EXTRA_TASK_DESCRIPTION);
+ if (taskDescription != null && Color.alpha(taskDescription.getPrimaryColor()) == 255) {
+ return taskDescription.getPrimaryColor();
+ } else {
+ // No task description. Use an organization color set by the policy controller.
+ final DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+ getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return devicePolicyManager.getOrganizationColorForUser(getTargetUserId());
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index e6483f6..a49c482 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -31,17 +31,21 @@
public class WorkLockActivityController {
private final Context mContext;
+ final SystemServicesProxy mSsp;
public WorkLockActivityController(Context context) {
mContext = context;
+ mSsp = SystemServicesProxy.getInstance(context);
+
EventBus.getDefault().register(this);
- SystemServicesProxy.getInstance(context).registerTaskStackListener(mLockListener);
+ mSsp.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
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 e8c0050..d832810 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -37,9 +37,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.PipSnapAlgorithm;
-import com.android.systemui.Dependency;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.tuner.TunerService;
import java.io.PrintWriter;
@@ -47,17 +45,18 @@
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
*/
-public class PipTouchHandler implements TunerService.Tunable {
+public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
// These values are used for metrics and should never change
private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
- private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
-
private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200;
+ // Allow dragging the PIP to a location to close it
+ private static final boolean ENABLE_DRAG_TO_DISMISS = false;
+
private final Context mContext;
private final IActivityManager mActivityManager;
private final IWindowManager mWindowManager;
@@ -70,9 +69,6 @@
private final PipDismissViewController mDismissViewController;
private final PipSnapAlgorithm mSnapAlgorithm;
- // Allow dragging the PIP to a location to close it
- private boolean mEnableDragToDismiss = false;
-
// The current movement bounds
private Rect mMovementBounds = new Rect();
@@ -86,7 +82,7 @@
private Runnable mShowDismissAffordance = new Runnable() {
@Override
public void run() {
- if (mEnableDragToDismiss) {
+ if (ENABLE_DRAG_TO_DISMISS) {
mDismissViewController.showDismissTarget(mMotionHelper.getBounds());
}
}
@@ -183,23 +179,6 @@
mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm,
mFlingAnimationUtils);
registerInputConsumer();
-
- // Register any tuner settings changes
- Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS);
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (newValue == null) {
- // Reset back to default
- mEnableDragToDismiss = false;
- return;
- }
- switch (key) {
- case TUNER_KEY_DRAG_TO_DISMISS:
- mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
- break;
- }
}
public void onActivityPinned() {
@@ -439,7 +418,7 @@
@Override
public void onDown(PipTouchState touchState) {
- if (mEnableDragToDismiss) {
+ if (ENABLE_DRAG_TO_DISMISS) {
mDismissViewController.createDismissTarget();
mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
}
@@ -451,7 +430,7 @@
mSavedSnapFraction = -1f;
}
- if (touchState.startedDragging() && mEnableDragToDismiss) {
+ if (touchState.startedDragging() && ENABLE_DRAG_TO_DISMISS) {
mHandler.removeCallbacks(mShowDismissAffordance);
mDismissViewController.showDismissTarget(mMotionHelper.getBounds());
}
@@ -469,7 +448,7 @@
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds);
- if (mEnableDragToDismiss) {
+ if (ENABLE_DRAG_TO_DISMISS) {
mDismissViewController.updateDismissTarget(mTmpBounds);
}
return true;
@@ -480,7 +459,7 @@
@Override
public boolean onUp(PipTouchState touchState) {
try {
- if (mEnableDragToDismiss) {
+ if (ENABLE_DRAG_TO_DISMISS) {
mHandler.removeCallbacks(mShowDismissAffordance);
PointF vel = mTouchState.getVelocity();
final float velocity = PointF.length(vel.x, vel.y);
@@ -583,7 +562,7 @@
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
- pw.println(innerPrefix + "mEnableDragToDismiss=" + mEnableDragToDismiss);
+ pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DRAG_TO_DISMISS);
mSnapAlgorithm.dump(pw, innerPrefix);
mTouchState.dump(pw, innerPrefix);
mMotionHelper.dump(pw, innerPrefix);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
similarity index 91%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
rename to packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
index dd1614b..e895fa2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -18,12 +18,10 @@
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -40,6 +38,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.plugins.VersionInfo.InvalidVersionException;
import java.util.ArrayList;
import java.util.List;
@@ -55,7 +54,7 @@
private final PluginListener<T> mListener;
private final String mAction;
private final boolean mAllowMultiple;
- private final int mVersion;
+ private final VersionInfo mVersion;
@VisibleForTesting
final MainHandler mMainHandler;
@@ -66,14 +65,14 @@
private final PluginManager mManager;
PluginInstanceManager(Context context, String action, PluginListener<T> listener,
- boolean allowMultiple, Looper looper, int version, PluginManager manager) {
+ boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) {
this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
manager, Build.IS_DEBUGGABLE);
}
@VisibleForTesting
PluginInstanceManager(Context context, PackageManager pm, String action,
- PluginListener<T> listener, boolean allowMultiple, Looper looper, int version,
+ PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
PluginManager manager, boolean debuggable) {
mMainHandler = new MainHandler(Looper.getMainLooper());
mPluginHandler = new PluginHandler(looper);
@@ -301,8 +300,14 @@
Context pluginContext = new PluginContextWrapper(
mContext.createApplicationContext(info, 0), classLoader);
Class<?> pluginClass = Class.forName(cls, true, classLoader);
+ // TODO: Only create the plugin before version check if we need it for
+ // legacy version check.
T plugin = (T) pluginClass.newInstance();
- if (plugin.getVersion() != mVersion) {
+ try {
+ checkVersion(pluginClass, plugin, mVersion);
+ if (DEBUG) Log.d(TAG, "createPlugin");
+ return new PluginInfo(pkg, cls, plugin, pluginContext);
+ } catch (InvalidVersionException e) {
final int icon = mContext.getResources().getIdentifier("tuner", "drawable",
mContext.getPackageName());
final int color = Resources.getSystem().getIdentifier(
@@ -318,20 +323,18 @@
String label = cls;
try {
label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
- } catch (NameNotFoundException e) {
+ } catch (NameNotFoundException e2) {
}
- if (plugin.getVersion() < mVersion) {
+ if (!e.isTooNew()) {
// Localization not required as this will never ever appear in a user build.
nb.setContentTitle("Plugin \"" + label + "\" is too old")
.setContentText("Contact plugin developer to get an updated"
- + " version.\nPlugin version: " + plugin.getVersion()
- + "\nSystem version: " + mVersion);
+ + " version.\n" + e.getMessage());
} else {
// Localization not required as this will never ever appear in a user build.
nb.setContentTitle("Plugin \"" + label + "\" is too new")
.setContentText("Check to see if an OTA is available.\n"
- + "Plugin version: " + plugin.getVersion()
- + "\nSystem version: " + mVersion);
+ + e.getMessage());
}
Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData(
Uri.parse("package://" + component.flattenToString()));
@@ -345,13 +348,24 @@
+ ", expected " + mVersion);
return null;
}
- if (DEBUG) Log.d(TAG, "createPlugin");
- return new PluginInfo(pkg, cls, plugin, pluginContext);
} catch (Exception e) {
Log.w(TAG, "Couldn't load plugin: " + pkg, e);
return null;
}
}
+
+ private void checkVersion(Class<?> pluginClass, T plugin, VersionInfo version)
+ throws InvalidVersionException {
+ VersionInfo pv = new VersionInfo().addClass(pluginClass);
+ if (pv.hasVersionInfo()) {
+ version.checkVersion(pv);
+ } else {
+ int fallbackVersion = plugin.getVersion();
+ if (fallbackVersion != version.getDefaultVersion()) {
+ throw new InvalidVersionException("Invalid legacy version", false);
+ }
+ }
+ }
}
public static class PluginContextWrapper extends ContextWrapper {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
similarity index 89%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
rename to packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
index cef485e..8b4bd7b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
@@ -33,6 +33,7 @@
import android.os.Looper;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -40,6 +41,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
import dalvik.system.PathClassLoader;
@@ -93,7 +95,18 @@
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
}
- public <T extends Plugin> T getOneShotPlugin(String action, int version) {
+ public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
+ ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
+ if (info == null) {
+ throw new RuntimeException(cls + " doesn't provide an interface");
+ }
+ if (TextUtils.isEmpty(info.action())) {
+ throw new RuntimeException(cls + " doesn't provide an action");
+ }
+ return getOneShotPlugin(info.action(), cls);
+ }
+
+ public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
if (!isDebuggable) {
// Never ever ever allow these on production builds, they are only for prototyping.
return null;
@@ -102,7 +115,7 @@
throw new RuntimeException("Must be called from UI thread");
}
PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
- false, mBackgroundThread.getLooper(), version, this);
+ false, mBackgroundThread.getLooper(), cls, this);
mPluginPrefs.addAction(action);
PluginInfo<T> info = p.getPlugin();
if (info != null) {
@@ -114,20 +127,36 @@
return null;
}
- public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- int version) {
- addPluginListener(action, listener, version, false);
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
+ addPluginListener(listener, cls, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ boolean allowMultiple) {
+ ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
+ if (info == null) {
+ throw new RuntimeException(cls + " doesn't provide an interface");
+ }
+ if (TextUtils.isEmpty(info.action())) {
+ throw new RuntimeException(cls + " doesn't provide an action");
+ }
+ addPluginListener(info.action(), listener, cls, allowMultiple);
}
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- int version, boolean allowMultiple) {
+ Class<?> cls) {
+ addPluginListener(action, listener, cls, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ Class cls, boolean allowMultiple) {
if (!isDebuggable) {
// Never ever ever allow these on production builds, they are only for prototyping.
return;
}
mPluginPrefs.addAction(action);
PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
- allowMultiple, mBackgroundThread.getLooper(), version, this);
+ allowMultiple, mBackgroundThread.getLooper(), cls, this);
p.loadAll();
mPluginMap.put(listener, p);
startListening();
@@ -282,9 +311,9 @@
public static class PluginInstanceManagerFactory {
public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
- int version, PluginManager manager) {
+ Class<?> cls, PluginManager manager) {
return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
- version, manager);
+ new VersionInfo().addClass(cls), manager);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java
similarity index 100%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java
rename to packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java
new file mode 100644
index 0000000..84f7761
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java
@@ -0,0 +1,134 @@
+/*
+ * 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.plugins;
+
+import com.android.systemui.plugins.annotations.Dependencies;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.annotations.Requirements;
+import com.android.systemui.plugins.annotations.Requires;
+
+import android.util.ArrayMap;
+
+public class VersionInfo {
+
+ private final ArrayMap<Class<?>, Version> mVersions = new ArrayMap<>();
+ private Class<?> mDefault;
+
+ public boolean hasVersionInfo() {
+ return !mVersions.isEmpty();
+ }
+
+ public int getDefaultVersion() {
+ return mVersions.get(mDefault).mVersion;
+ }
+
+ public VersionInfo addClass(Class<?> cls) {
+ if (mDefault == null) {
+ // The legacy default version is from the first class we add.
+ mDefault = cls;
+ }
+ addClass(cls, false);
+ return this;
+ }
+
+ private void addClass(Class<?> cls, boolean required) {
+ ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class);
+ if (provider != null) {
+ mVersions.put(cls, new Version(provider.version(), true));
+ }
+ Requires requires = cls.getDeclaredAnnotation(Requires.class);
+ if (requires != null) {
+ mVersions.put(requires.target(), new Version(requires.version(), required));
+ }
+ Requirements requirements = cls.getDeclaredAnnotation(Requirements.class);
+ if (requirements != null) {
+ for (Requires r : requirements.value()) {
+ mVersions.put(r.target(), new Version(r.version(), required));
+ }
+ }
+ DependsOn depends = cls.getDeclaredAnnotation(DependsOn.class);
+ if (depends != null) {
+ addClass(depends.target(), true);
+ }
+ Dependencies dependencies = cls.getDeclaredAnnotation(Dependencies.class);
+ if (dependencies != null) {
+ for (DependsOn d : dependencies.value()) {
+ addClass(d.target(), true);
+ }
+ }
+ }
+
+ public void checkVersion(VersionInfo plugin) throws InvalidVersionException {
+ ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions);
+ plugin.mVersions.forEach((aClass, version) -> {
+ Version v = versions.remove(aClass);
+ if (v == null) {
+ v = createVersion(aClass);
+ }
+ if (v == null) {
+ throw new InvalidVersionException(aClass.getSimpleName()
+ + " does not provide an interface", false);
+ }
+ if (v.mVersion != version.mVersion) {
+ throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, v.mVersion,
+ version.mVersion);
+ }
+ });
+ versions.forEach((aClass, version) -> {
+ if (version.mRequired) {
+ throw new InvalidVersionException("Missing required dependency "
+ + aClass.getSimpleName(), false);
+ }
+ });
+ }
+
+ private Version createVersion(Class<?> cls) {
+ ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class);
+ if (provider != null) {
+ return new Version(provider.version(), false);
+ }
+ return null;
+ }
+
+ public static class InvalidVersionException extends RuntimeException {
+ private final boolean mTooNew;
+
+ public InvalidVersionException(String str, boolean tooNew) {
+ super(str);
+ mTooNew = tooNew;
+ }
+
+ public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) {
+ super(cls.getSimpleName() + " expected version " + expected + " but had " + actual);
+ mTooNew = tooNew;
+ }
+
+ public boolean isTooNew() {
+ return mTooNew;
+ }
+ }
+
+ private static class Version {
+
+ private final int mVersion;
+ private final boolean mRequired;
+
+ public Version(int version, boolean required) {
+ mVersion = version;
+ mRequired = required;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index a231e79..3559257 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -242,7 +242,7 @@
maxHeight = height;
}
}
- setMeasuredDimension(getMeasuredWidth(), maxHeight);
+ setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
}
private final Runnable mDistribute = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index c85f83b..e0d1cba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -160,7 +160,9 @@
mAllViews.clear();
mTopFiveQs.clear();
- mAllViews.add((View) mQsPanel.getTileLayout());
+ QSTileLayout tileLayout = mQsPanel.getTileLayout();
+ mAllViews.add((View) tileLayout);
+ firstPageBuilder.addFloat(tileLayout, "translationY", mQsPanel.getHeight(), 0);
for (QSTile<?> tile : tiles) {
QSTileBaseView tileView = mQsPanel.getTileView(tile);
@@ -168,7 +170,6 @@
Log.e(TAG, "tileView is null " + tile.getTileSpec());
continue;
}
- final TextView label = ((QSTileView) tileView).getLabel();
final View tileIcon = tileView.getIcon().getIconView();
View view = mQs.getView();
if (count < mNumQuickTiles && mAllowFancy) {
@@ -187,12 +188,12 @@
// Counteract the parent translation on the tile. So we have a static base to
// animate the label position off from.
- firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
+ //firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
- // Move the real tile's label from the quick tile position to its final
+ // Move the real tile from the quick tile position to its final
// location.
- translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
- translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
+ translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
+ translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
mTopFiveQs.add(tileView.getIcon());
mAllViews.add(tileView.getIcon());
@@ -209,22 +210,22 @@
firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
- translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
+ translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
mAllViews.add(tileIcon);
} else {
firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
+ firstPageBuilder.addFloat(tileView, "translationY", -mQsPanel.getHeight(), 0);
}
mAllViews.add(tileView);
- mAllViews.add(label);
count++;
}
if (mAllowFancy) {
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
if (brightness != null) {
-// firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
+ firstPageBuilder.addFloat(brightness, "translationY", mQsPanel.getHeight(), 0);
mBrightnessAnimator = new TouchAnimator.Builder()
.addFloat(brightness, "alpha", 0, 1)
.addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
@@ -240,7 +241,7 @@
// Fade in the tiles/labels as we reach the final position.
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
- .addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1)
+ .addFloat(tileLayout, "alpha", 0, 1)
.addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
mAllViews.add(mQsPanel.getFooter().getView());
float px = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 504678c..e8d5ece 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -86,6 +86,10 @@
setOrientation(VERTICAL);
+ mBrightnessView = LayoutInflater.from(context).inflate(
+ R.layout.quick_settings_brightness_dialog, this, false);
+ addView(mBrightnessView);
+
setupTileLayout();
mFooter = new QSFooter(this, context);
@@ -100,10 +104,6 @@
updateResources();
- mBrightnessView = LayoutInflater.from(context).inflate(
- R.layout.quick_settings_brightness_dialog, this, false);
- addView(mBrightnessView);
-
mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSliderView) findViewById(R.id.brightness_slider));
@@ -441,8 +441,15 @@
}
r.tile.setDetailListening(show);
int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
- int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2
- + getTop();
+ int y;
+ if (r.tileView instanceof QSTileView) {
+ View labelContainer = (View) ((QSTileView) r.tileView).getLabel().getParent();
+ y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + getTop()
+ + labelContainer.getTop() + labelContainer.getHeight() / 2;
+ } else {
+ y = r.tileView.getTop() + mTileLayout.getOffsetTop(r) + r.tileView.getHeight() / 2
+ + getTop();
+ }
handleShowDetailImpl(r, show, x, y);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
index 0e04d0a..c750fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
@@ -15,38 +15,30 @@
*/
package com.android.systemui.qs;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.service.quicksettings.Tile;
import android.text.TextUtils;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.Switch;
-import com.android.settingslib.Utils;
-
import com.android.systemui.R;
public class QSTileBaseView extends LinearLayout {
private static final String TAG = "QSTileBaseView";
private final H mHandler = new H();
+ private final FrameLayout mIconFrame;
protected QSIconView mIcon;
protected RippleDrawable mRipple;
private Drawable mTileBackground;
@@ -63,15 +55,15 @@
// Default to Quick Tile padding, and QSTileView will specify its own padding.
int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
- FrameLayout frame = new FrameLayout(context);
- frame.setForegroundGravity(Gravity.CENTER);
+ mIconFrame = new FrameLayout(context);
+ mIconFrame.setForegroundGravity(Gravity.CENTER);
int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
- addView(frame, new LayoutParams(size, size));
+ addView(mIconFrame, new LayoutParams(size, size));
mIcon = icon;
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, padding, 0, padding);
- frame.addView(mIcon, params);
+ mIconFrame.addView(mIcon, params);
mTileBackground = newTileBackground();
if (mTileBackground instanceof RippleDrawable) {
@@ -105,7 +97,7 @@
private void updateRippleSize(int width, int height) {
// center the touch feedback on the center of the icon, and dial it down a bit
final int cx = width / 2;
- final int cy = height / 2;
+ final int cy = mIconFrame.getMeasuredHeight() / 2;
final int rad = (int) (mIcon.getHeight() * .85f);
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 232941d..428fe9b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.drawable.RippleDrawable;
import android.service.quicksettings.Tile;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
@@ -28,6 +29,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;
@@ -43,8 +45,7 @@
protected TextView mLabel;
private ImageView mPadLock;
private int mState;
- private OnClickListener mClick;
- private OnClickListener mSecondaryClick;
+ private ViewGroup mLabelContainer;
public QSTileView(Context context, QSIconView icon) {
this(context, icon, false);
@@ -76,13 +77,15 @@
}
protected void createLabel() {
- ViewGroup view = (ViewGroup) LayoutInflater.from(getContext())
- .inflate(R.layout.qs_tile_label, null);
- view.setClipChildren(false);
- view.setClipToPadding(false);
- mLabel = (TextView) view.findViewById(R.id.tile_label);
- mPadLock = (ImageView) view.findViewById(R.id.restricted_padlock);
- addView(view);
+ mLabelContainer = (ViewGroup) LayoutInflater.from(getContext())
+ .inflate(R.layout.qs_tile_label, this, false);
+ mLabelContainer.setClipChildren(false);
+ mLabelContainer.setClipToPadding(false);
+ mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label);
+ mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock);
+
+ mLabelContainer.setBackground(newTileBackground());
+ addView(mLabelContainer);
}
@Override
@@ -104,17 +107,10 @@
}
@Override
- public void init(OnClickListener click, OnClickListener secondaryClick, OnLongClickListener longClick) {
- mClick = click;
- mSecondaryClick = secondaryClick;
+ public void init(OnClickListener click, OnClickListener secondaryClick,
+ OnLongClickListener longClick) {
super.init(click, secondaryClick, longClick);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- setOnClickListener(event.getY() < (getMeasuredHeight() / 2) ? mClick : mSecondaryClick);
- }
- return super.onTouchEvent(event);
+ mLabelContainer.setClickable(true);
+ mLabelContainer.setOnClickListener(secondaryClick);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index f2c3e61..4e30797 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -301,6 +301,7 @@
if (position == mEditIndex) position--;
move(mAccessibilityFromIndex, position, v);
+
notifyDataSetChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index ce72942..ec4ca7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -102,12 +102,7 @@
mTokenMap.remove(service.getToken());
mTiles.remove(tile.getComponent());
final String slot = tile.getComponent().getClassName();
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getIconController().removeIcon(slot);
- }
- });
+ mMainHandler.post(() -> mHost.getIconController().removeIcon(slot));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index fff8305..f7bfc1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -35,8 +35,8 @@
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.BatteryInfo;
+import com.android.settingslib.graph.BatteryMeterDrawableBase;
import com.android.settingslib.graph.UsageView;
-import com.android.systemui.BatteryMeterDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
@@ -104,11 +104,6 @@
}
@Override
- protected void handleSecondaryClick() {
- showDetail(true);
- }
-
- @Override
public CharSequence getTileLabel() {
return mContext.getString(R.string.battery);
}
@@ -118,7 +113,6 @@
int level = (arg != null) ? (Integer) arg : mLevel;
String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
- state.dualTarget = true;
state.state = mCharging ? Tile.STATE_UNAVAILABLE
: mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver);
@@ -155,8 +149,10 @@
private final class BatteryDetail implements DetailAdapter, OnClickListener,
OnAttachStateChangeListener {
- private final BatteryMeterDrawable mDrawable = new BatteryMeterDrawable(mHost.getContext(),
- mHost.getContext().getColor(R.color.batterymeter_frame_color));
+ private final BatteryMeterDrawableBase mDrawable
+ = new BatteryMeterDrawableBase(
+ mHost.getContext(),
+ mHost.getContext().getColor(R.color.batterymeter_frame_color));
private View mCurrentView;
@Override
@@ -195,8 +191,9 @@
if (mCurrentView == null) {
return;
}
- mDrawable.onBatteryLevelChanged(100, false, false);
- mDrawable.onPowerSaveChanged(true);
+ mDrawable.setBatteryLevel(100);
+ mDrawable.setPluggedIn(false);
+ mDrawable.setPowerSave(true);
mDrawable.setShowPercent(false);
((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index b48b829..3c28f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -123,7 +123,6 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.dualTarget = true;
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.contentDescription = state.label;
state.value = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index ae4d6c9..7e6bb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -61,7 +61,7 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_SYNC_SETTINGS);
+ return new Intent(Settings.ACTION_MANAGED_PROFILE_SETTINGS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index a6fe0ea..c9debb2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -31,6 +31,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -44,7 +45,6 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
import com.android.keyguard.LatencyTracker;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
@@ -181,8 +181,10 @@
// is still valid. Otherwise, we need to reset the lastStackactiveTime to the
// currentTime and remove the old tasks in between which would not be previously
// visible, but currently would be in the new currentTime
- long oldLastStackActiveTime = Prefs.getLong(RecentsActivity.this,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
+ int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this)
+ .getCurrentUser();
+ long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser);
if (oldLastStackActiveTime != -1) {
long currentTime = System.currentTimeMillis();
if (currentTime < oldLastStackActiveTime) {
@@ -200,8 +202,8 @@
Recents.getSystemServices().removeTask(task.persistentId);
}
}
- Prefs.putLong(RecentsActivity.this,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, currentTime);
+ Settings.Secure.putLongForUser(RecentsActivity.this.getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, currentTime, currentUser);
}
}
}
@@ -834,8 +836,9 @@
Recents.getTaskLoader().dump(prefix, writer);
String id = Integer.toHexString(System.identityHashCode(this));
- long lastStackActiveTime = Prefs.getLong(this,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
+ long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1,
+ SystemServicesProxy.getInstance(this).getCurrentUser());
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 55491b2..8de4e58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -130,6 +130,7 @@
launchOpts.numVisibleTaskThumbnails = 2;
launchOpts.onlyLoadForCache = true;
launchOpts.onlyLoadPausedActivities = true;
+ launchOpts.loadThumbnails = !ActivityManager.ENABLE_TASK_SNAPSHOTS;
loader.loadTasks(mContext, plan, launchOpts);
}
}
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 49074a6..eae1b81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -868,6 +868,14 @@
return null;
}
+ public ActivityManager.TaskDescription getTaskDescription(int taskId) {
+ try {
+ return mIam.getTaskDescription(taskId);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
/**
* Returns the given icon for a user, badging if necessary.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 11b5984..12c10df 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -26,6 +26,8 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -130,6 +132,7 @@
preloadRawTasks(includeFrontMostExcludedTask);
}
+ SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
SparseIntArray affiliatedTaskCounts = new SparseIntArray();
SparseBooleanArray lockedUsers = new SparseBooleanArray();
@@ -137,8 +140,10 @@
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
- long lastStackActiveTime = Prefs.getLong(mContext,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
+ int currentUserId = ssp.getCurrentUser();
+ long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
+ long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
if (RecentsDebugFlags.Static.EnableMockTasks) {
lastStackActiveTime = 0;
}
@@ -205,8 +210,8 @@
affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
- Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
- newLastStackActiveTime);
+ Settings.Secure.putLongForUser(mContext.getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
}
// Initialize the stacks
@@ -285,4 +290,36 @@
private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
}
+
+
+ /**
+ * Migrate the last active time from the prefs to the secure settings.
+ *
+ * The first time this runs, it will:
+ * 1) fetch the last stack active time from the prefs
+ * 2) set the prefs to the last stack active time for all users
+ * 3) clear the pref
+ * 4) return the last stack active time
+ *
+ * Subsequent calls to this will return zero.
+ */
+ private long migrateLegacyLastStackActiveTime(int currentUserId) {
+ long legacyLastStackActiveTime = Prefs.getLong(mContext,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
+ if (legacyLastStackActiveTime != -1) {
+ Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
+ UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ List<UserInfo> users = userMgr.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ int userId = users.get(i).id;
+ if (userId != currentUserId) {
+ Settings.Secure.putLongForUser(mContext.getContentResolver(),
+ Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime,
+ userId);
+ }
+ }
+ return legacyLastStackActiveTime;
+ }
+ return 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 0160eb7..40aad45 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,6 +20,8 @@
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
@@ -1496,11 +1498,6 @@
// Remove the task from the ignored set
removeIgnoreTask(removedTask);
- // Resize the grid layout task view focus frame
- if (mTaskViewFocusFrame != null) {
- mTaskViewFocusFrame.resize();
- }
-
// If requested, relayout with the given animation
if (animation != null) {
updateLayoutAlgorithm(true /* boundScroll */);
@@ -1838,6 +1835,17 @@
announceForAccessibility(getContext().getString(
R.string.accessibility_recents_item_dismissed, event.task.title));
+ if (useGridLayout() && event.animation != null) {
+ event.animation.setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animator) {
+ if (mTaskViewFocusFrame != null) {
+ // Resize the grid layout task view focus frame
+ mTaskViewFocusFrame.resize();
+ }
+ }
+ });
+ }
+
// Remove the task from the stack
mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9a52a7b..a5f7832 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -270,7 +270,6 @@
return super.onInterceptTouchEvent(ev);
}
-
@Override
protected void measureContents(int width, int height) {
int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 4ac0f9e..02b0113 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -77,10 +77,11 @@
@ViewDebug.ExportedProperty(category="recents")
private float mDimAlpha;
private Matrix mMatrix = new Matrix();
- protected Paint mDrawPaint = new Paint();
- private Paint mLockedPaint = new Paint();
+ private Paint mDrawPaint = new Paint();
+ protected Paint mLockedPaint = new Paint();
protected Paint mBgFillPaint = new Paint();
protected BitmapShader mBitmapShader;
+ protected boolean mUserLocked = false;
private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
// Clip the top of the thumbnail against the opaque header bar that overlaps this view
@@ -152,7 +153,7 @@
int thumbnailHeight = Math.min(viewHeight,
(int) (mThumbnailRect.height() * mThumbnailScale));
- if (mTask != null && mTask.isLocked) {
+ if (mUserLocked) {
canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
mLockedPaint);
} else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
@@ -344,6 +345,17 @@
}
/**
+ * Returns the {@link Paint} used to draw a task screenshot, or {@link #mLockedPaint} if the
+ * thumbnail shouldn't be drawn because it belongs to a locked user.
+ */
+ protected Paint getDrawPaint() {
+ if (mUserLocked) {
+ return mLockedPaint;
+ }
+ return mDrawPaint;
+ }
+
+ /**
* Binds the thumbnail view to the task.
*/
void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) {
@@ -354,7 +366,10 @@
if (t.colorBackground != 0) {
mBgFillPaint.setColor(t.colorBackground);
}
- mLockedPaint.setColor(t.colorPrimary);
+ if (t.colorPrimary != 0) {
+ mLockedPaint.setColor(t.colorPrimary);
+ }
+ mUserLocked = t.isLocked;
EventBus.getDefault().register(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
index 2c3e42b..bcf4f17 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -28,7 +28,6 @@
private Path mThumbnailOutline;
private Path mRestBackgroundOutline;
- private Path mFullBackgroundOutline;
// True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
// be updated.
private boolean mUpdateThumbnailOutline = true;
@@ -145,10 +144,7 @@
90, 90, false); // F
mRestBackgroundOutline.lineTo(l, t); // A
mRestBackgroundOutline.close();
-
}
- } else {
- mFullBackgroundOutline = mThumbnailOutline;
}
}
@@ -167,7 +163,10 @@
updateThumbnailOutline();
mUpdateThumbnailOutline = false;
}
- if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+
+ if (mUserLocked) {
+ canvas.drawPath(mThumbnailOutline, mLockedPaint);
+ } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
// Draw the background, there will be some small overdraw with the thumbnail
if (thumbnailWidth < viewWidth) {
// Portrait thumbnail on a landscape task view
@@ -177,9 +176,9 @@
// Landscape thumbnail on a portrait task view
canvas.drawPath(mRestBackgroundOutline, mBgFillPaint);
}
- canvas.drawPath(mThumbnailOutline, mDrawPaint);
+ canvas.drawPath(mThumbnailOutline, getDrawPaint());
} else {
- canvas.drawPath(mFullBackgroundOutline, mBgFillPaint);
+ canvas.drawPath(mThumbnailOutline, mBgFillPaint);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 5366da1..995901b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -164,15 +164,19 @@
}
}
- public void disable(int state1, int state2) {
+ public void disable(int state1, int state2, boolean animate) {
synchronized (mLock) {
mDisable1 = state1;
mDisable2 = state2;
mHandler.removeMessages(MSG_DISABLE);
- mHandler.obtainMessage(MSG_DISABLE, state1, state2, null).sendToTarget();
+ mHandler.obtainMessage(MSG_DISABLE, state1, state2, animate).sendToTarget();
}
}
+ public void disable(int state1, int state2) {
+ disable(state1, state2, true);
+ }
+
public void animateExpandNotificationsPanel() {
synchronized (mLock) {
mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);
@@ -433,7 +437,7 @@
}
case MSG_DISABLE:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).disable(msg.arg1, msg.arg2, true /* animate */);
+ mCallbacks.get(i).disable(msg.arg1, msg.arg2, (Boolean) msg.obj);
}
break;
case MSG_EXPAND_NOTIFICATIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index a2b0de5..bfe4bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,7 +22,6 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -76,6 +75,7 @@
private int mNotificationMinHeightLegacy;
private int mMaxHeadsUpHeightLegacy;
private int mMaxHeadsUpHeight;
+ private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeight;
private int mNotificationMinHeightLarge;
private int mNotificationMaxHeight;
@@ -209,6 +209,9 @@
private boolean mIsLowPriority;
private boolean mIsColorized;
private boolean mUseIncreasedCollapsedHeight;
+ private boolean mUseIncreasedHeadsUpHeight;
+ private float mTranslationWhenRemoved;
+ private boolean mWasChildInGroupWhenRemoved;
@Override
public boolean isGroupExpansionChanging() {
@@ -327,10 +330,11 @@
boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
NotificationColorUtil.getInstance(mContext));
+ int color = StatusBarIconView.NO_COLOR;
if (colorize) {
- int color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
- expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+ color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
}
+ expandedIcon.setStaticDrawableColor(color);
}
private void updateLimits() {
@@ -354,8 +358,14 @@
boolean headsUpCustom = layout.getHeadsUpChild() != null &&
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
- int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
- : mMaxHeadsUpHeight;
+ int headsUpheight;
+ if (headsUpCustom && beforeN) {
+ headsUpheight = mMaxHeadsUpHeightLegacy;
+ } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
+ headsUpheight = mMaxHeadsUpHeightIncreased;
+ } else {
+ headsUpheight = mMaxHeadsUpHeight;
+ }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -452,6 +462,7 @@
updateBackgroundForGroupState();
updateClickAndFocus();
if (mNotificationParent != null) {
+ setOverrideTintColor(NO_COLOR, 0.0f);
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
@@ -828,10 +839,22 @@
public void setRemoved() {
mRemoved = true;
-
+ mTranslationWhenRemoved = getTranslationY();
+ mWasChildInGroupWhenRemoved = isChildInGroup();
+ if (isChildInGroup()) {
+ mTranslationWhenRemoved += getNotificationParent().getTranslationY();
+ }
mPrivateLayout.setRemoved();
}
+ public boolean wasChildInGroupWhenRemoved() {
+ return mWasChildInGroupWhenRemoved;
+ }
+
+ public float getTranslationWhenRemoved() {
+ return mTranslationWhenRemoved;
+ }
+
public NotificationChildrenContainer getChildrenContainer() {
return mChildrenContainer;
}
@@ -994,6 +1017,10 @@
mUseIncreasedCollapsedHeight = use;
}
+ public void setUseIncreasedHeadsUpHeight(boolean use) {
+ mUseIncreasedHeadsUpHeight = use;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -1008,12 +1035,14 @@
mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
mNotificationMinHeightLarge = getFontScaledHeight(
- R.dimen.notification_min_height_large);
+ R.dimen.notification_min_height_increased);
mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
mMaxHeadsUpHeightLegacy = getFontScaledHeight(
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
+ mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+ R.dimen.notification_max_heads_up_height_increased);
mIncreasedPaddingBetweenElements = getResources()
.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize(
@@ -1965,7 +1994,7 @@
mAboveShelf = aboveShelf;
}
- public class NotificationViewState extends ExpandableViewState {
+ public static class NotificationViewState extends ExpandableViewState {
private final StackScrollState mOverallState;
@@ -1986,8 +2015,11 @@
@Override
protected void onYTranslationAnimationFinished(View view) {
super.onYTranslationAnimationFinished(view);
- if (mHeadsupDisappearRunning) {
- setHeadsUpAnimatingAway(false);
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (row.isHeadsUpAnimatingAway()) {
+ row.setHeadsUpAnimatingAway(false);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index bd5fb92..0ea56b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -381,12 +381,7 @@
if (appShortcuts != null) {
result.add(appShortcuts);
}
- synchronized (sLock) {
- // showKeyboardShortcutsDialog only if it has not been dismissed already
- if (sInstance != null) {
- showKeyboardShortcutsDialog(result);
- }
- }
+ showKeyboardShortcutsDialog(result);
}
}, deviceId);
}
@@ -585,7 +580,12 @@
mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
- mKeyboardShortcutsDialog.show();
+ synchronized (sLock) {
+ // showKeyboardShortcutsDialog only if it has not been dismissed already
+ if (sInstance != null) {
+ mKeyboardShortcutsDialog.show();
+ }
+ }
}
private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index d599ec1..bc992d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -87,6 +87,7 @@
private KeyguardUpdateMonitorCallback mUpdateMonitor;
private final DevicePolicyManager mDevicePolicyManager;
+ private boolean mDozing;
public KeyguardIndicationController(Context context, ViewGroup indicationArea,
LockIcon lockIcon) {
@@ -139,7 +140,7 @@
return;
}
- if (mDevicePolicyManager.isDeviceManaged()) {
+ if (!mDozing && mDevicePolicyManager.isDeviceManaged()) {
final CharSequence organizationName =
mDevicePolicyManager.getDeviceOwnerOrganizationName();
if (organizationName != null) {
@@ -224,6 +225,18 @@
if (mVisible) {
// Walk down a precedence-ordered list of what should indication
// should be shown based on user or device state
+ if (mDozing) {
+ // If we're dozing, never show a persistent indication.
+ if (!TextUtils.isEmpty(mTransientIndication)) {
+ mTextView.switchIndication(mTransientIndication);
+ mTextView.setTextColor(mTransientTextColor);
+
+ } else {
+ mTextView.switchIndication(null);
+ }
+ return;
+ }
+
if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) {
mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
mTextView.setTextColor(Color.WHITE);
@@ -319,6 +332,12 @@
}
};
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ updateIndication();
+ updateDisclosure();
+ }
+
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
private int mLastSuccessiveErrorMessage = -1;
@@ -349,7 +368,8 @@
int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null);
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor);
- } else if (updateMonitor.isDeviceInteractive()) {
+ } else if (updateMonitor.isDeviceInteractive()
+ || mDozing && updateMonitor.isScreenOn()) {
mLockIcon.setTransientFpError(true);
showTransientIndication(helpString, errorColor);
mHandler.removeMessages(MSG_CLEAR_FP_MSG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index f73a5ea..81db429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -126,7 +126,8 @@
}
public boolean cacheContentViews(Context ctx, Notification updatedNotification,
- boolean isLowPriority, boolean useIncreasedCollapsedView) {
+ boolean isLowPriority, boolean useIncreasedCollapsedView,
+ boolean useIncreasedHeadsUp) {
boolean applyInPlace = false;
if (updatedNotification != null) {
final Notification.Builder updatedNotificationBuilder
@@ -136,7 +137,7 @@
final RemoteViews newBigContentView = createBigContentView(
updatedNotificationBuilder, isLowPriority);
final RemoteViews newHeadsUpContentView =
- updatedNotificationBuilder.createHeadsUpContentView();
+ updatedNotificationBuilder.createHeadsUpContentView(useIncreasedHeadsUp);
final RemoteViews newPublicNotification
= updatedNotificationBuilder.makePublicContentView();
final RemoteViews newAmbientNotification
@@ -165,7 +166,7 @@
cachedContentView = createContentView(builder, isLowPriority,
useIncreasedCollapsedView);
cachedBigContentView = createBigContentView(builder, isLowPriority);
- cachedHeadsUpContentView = builder.createHeadsUpContentView();
+ cachedHeadsUpContentView = builder.createHeadsUpContentView(useIncreasedHeadsUp);
cachedPublicContentView = builder.makePublicContentView();
cachedAmbientContentView = builder.makeAmbientNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 355022f..534a719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -90,8 +90,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Dependency.get(PluginManager.class).addPluginListener(
- NotificationMenuRowProvider.ACTION, this,
- NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
+ this, NotificationMenuRowProvider.class, false /* Allow multiple */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 61574a6..1cd909ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -415,7 +416,8 @@
transitionAmount);
float shelfIconSize = icon.getHeight() * icon.getIconScale();
float alpha = 1.0f;
- if (!row.isShowingIcon()) {
+ boolean noIcon = !row.isShowingIcon();
+ if (noIcon) {
// The view currently doesn't have an icon, lets transform it in!
alpha = transitionAmount;
notificationIconSize = shelfIconSize / 2.0f;
@@ -439,6 +441,13 @@
if (row.isAboveShelf() || (!row.isInShelf() && isLastChild && row.areGutsExposed())) {
iconState.hidden = true;
}
+ int shelfColor = icon.getStaticDrawableColor();
+ if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
+ int notificationColor = row.getNotificationHeader().getOriginalNotificationColor();
+ shelfColor = NotificationUtils.interpolateColors(notificationColor, shelfColor,
+ iconState.iconAppearAmount);
+ }
+ iconState.iconColor = shelfColor;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 6283148..aec9a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -19,9 +19,11 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -37,7 +39,6 @@
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
-import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
@@ -50,6 +51,9 @@
import java.text.NumberFormat;
public class StatusBarIconView extends AnimatedImageView {
+ public static final int NO_COLOR = 0;
+ private final int ANIMATION_DURATION_FAST = 100;
+
public static final int STATE_ICON = 0;
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
@@ -104,6 +108,17 @@
private ObjectAnimator mDotAnimator;
private float mDotAppearAmount;
private OnVisibilityChangedListener mOnVisibilityChangedListener;
+ private int mDrawableColor;
+ private int mIconColor;
+ private ValueAnimator mColorAnimator;
+ private int mCurrentSetColor = NO_COLOR;
+ private int mAnimationStartColor = NO_COLOR;
+ private final ValueAnimator.AnimatorUpdateListener mColorUpdater
+ = animation -> {
+ int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor,
+ animation.getAnimatedFraction());
+ setColorInternal(newColor);
+ };
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -123,7 +138,7 @@
setScaleType(ScaleType.CENTER);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
if (mNotification != null) {
- setIconTint(getContext().getColor(
+ setDecorColor(getContext().getColor(
com.android.internal.R.color.notification_icon_default_color));
}
reloadDimens();
@@ -446,10 +461,66 @@
return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
}
- public void setIconTint(int iconTint) {
+ /**
+ * Set the color that is used to draw decoration like the overflow dot. This will not be applied
+ * to the drawable.
+ */
+ public void setDecorColor(int iconTint) {
mDotPaint.setColor(iconTint);
}
+ /**
+ * Set the static color that should be used for the drawable of this icon if it's not
+ * transitioning this also immediately sets the color.
+ */
+ public void setStaticDrawableColor(int color) {
+ mDrawableColor = color;
+ setColorInternal(color);
+ mIconColor = color;
+ }
+
+ private void setColorInternal(int color) {
+ if (color != NO_COLOR) {
+ setImageTintList(ColorStateList.valueOf(color));
+ } else {
+ setImageTintList(null);
+ }
+ mCurrentSetColor = color;
+ }
+
+ public void setIconColor(int iconColor, boolean animate) {
+ if (mIconColor != iconColor) {
+ mIconColor = iconColor;
+ if (mColorAnimator != null) {
+ mColorAnimator.cancel();
+ }
+ if (mCurrentSetColor == iconColor) {
+ return;
+ }
+ if (animate && mCurrentSetColor != NO_COLOR) {
+ mAnimationStartColor = mCurrentSetColor;
+ mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mColorAnimator.setDuration(ANIMATION_DURATION_FAST);
+ mColorAnimator.addUpdateListener(mColorUpdater);
+ mColorAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mColorAnimator = null;
+ mAnimationStartColor = NO_COLOR;
+ }
+ });
+ mColorAnimator.start();
+ } else {
+ setColorInternal(iconColor);
+ }
+ }
+ }
+
+ public int getStaticDrawableColor() {
+ return mDrawableColor;
+ }
+
public void setVisibleState(int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
@@ -467,10 +538,13 @@
boolean runnableAdded = false;
if (visibleState != mVisibleState) {
mVisibleState = visibleState;
+ if (mIconAppearAnimator != null) {
+ mIconAppearAnimator.cancel();
+ }
+ if (mDotAnimator != null) {
+ mDotAnimator.cancel();
+ }
if (animate) {
- if (mIconAppearAnimator != null) {
- mIconAppearAnimator.cancel();
- }
float targetAmount = 0.0f;
Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_ICON) {
@@ -482,7 +556,7 @@
mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
currentAmount, targetAmount);
mIconAppearAnimator.setInterpolator(interpolator);
- mIconAppearAnimator.setDuration(100);
+ mIconAppearAnimator.setDuration(ANIMATION_DURATION_FAST);
mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -494,9 +568,6 @@
runnableAdded = true;
}
- if (mDotAnimator != null) {
- mDotAnimator.cancel();
- }
targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
interpolator = Interpolators.FAST_OUT_LINEAR_IN;
if (visibleState == STATE_DOT) {
@@ -508,7 +579,7 @@
mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
currentAmount, targetAmount);
mDotAnimator.setInterpolator(interpolator);
- mDotAnimator.setDuration(100);
+ mDotAnimator.setDuration(ANIMATION_DURATION_FAST);
final boolean runRunnable = !runnableAdded;
mDotAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 1c89e32..5353005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -224,9 +224,6 @@
stack.push(viewRoot);
while (!stack.isEmpty()) {
View child = stack.pop();
- if (child.getVisibility() == View.GONE) {
- continue;
- }
Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
if (containsView == null) {
// This one is unhandled, let's add it to our list.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index d15ab10..c3f1cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -69,7 +69,8 @@
public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- if (mTransformedView.getVisibility() == View.INVISIBLE) {
+ if (mTransformedView.getVisibility() == View.INVISIBLE
+ || mTransformedView.getAlpha() != 1.0f) {
// We have the same content, lets show ourselves
mTransformedView.setAlpha(1.0f);
mTransformedView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index a4e5916..8dab069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -72,6 +72,9 @@
* @param pulsing whether we are currently pulsing for ambient display.
*/
public void setPulsing(boolean pulsing) {
+ if (mPulsing == pulsing) {
+ return;
+ }
mPulsing = pulsing;
updateReorderingAllowed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 0e074c7..883a66b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -31,6 +31,7 @@
public class DozeParameters {
private static final int MAX_DURATION = 60 * 1000;
+ public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully";
private final Context mContext;
@@ -56,6 +57,10 @@
pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
pw.print(" getPickupSubtypePerformsProxCheck(): ");pw.println(
dumpPickupSubtypePerformsProxCheck());
+ if (Build.IS_DEBUGGABLE) {
+ pw.print(" getAlwaysOn(): "); pw.println(getAlwaysOn());
+ pw.print(" getSensorsWakeUpFully(): "); pw.println(getSensorsWakeUpFully());
+ }
}
private String dumpPickupSubtypePerformsProxCheck() {
@@ -118,6 +123,12 @@
Settings.Secure.DOZE_ALWAYS_ON, 0, UserHandle.USER_CURRENT) != 0;
}
+ public boolean getSensorsWakeUpFully() {
+ return Build.IS_DEBUGGABLE
+ && Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ DOZE_SENSORS_WAKE_UP_FULLY, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
private boolean getBoolean(String propName, int resId) {
return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 2836f41..8f63d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -167,6 +167,7 @@
private IntentButton mLeftPlugin;
private String mLeftButtonStr;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private boolean mDozing;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -261,9 +262,9 @@
super.onAttachedToWindow();
mAccessibilityController.addStateChangedCallback(this);
Dependency.get(PluginManager.class).addPluginListener(RIGHT_BUTTON_PLUGIN,
- mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ mRightListener, IntentButtonProvider.class, false /* Only allow one */);
Dependency.get(PluginManager.class).addPluginListener(LEFT_BUTTON_PLUGIN,
- mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ mLeftListener, IntentButtonProvider.class, false /* Only allow one */);
Dependency.get(TunerService.class).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON);
}
@@ -361,7 +362,7 @@
// Things are not set up yet; reply hazy, ask again later
return;
}
- mRightAffordanceView.setVisibility(mRightButton.getIcon().isVisible
+ mRightAffordanceView.setVisibility(!mDozing && mRightButton.getIcon().isVisible
? View.VISIBLE : View.GONE);
}
@@ -375,7 +376,7 @@
private void updateLeftAffordanceIcon() {
IconState state = mLeftButton.getIcon();
- mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ mLeftAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
mLeftAffordanceView.setContentDescription(state.contentDescription);
}
@@ -846,6 +847,22 @@
}
};
+ public void setDozing(boolean dozing, boolean animate) {
+ mDozing = dozing;
+
+ updateCameraVisibility();
+ updateLeftAffordanceIcon();
+
+ if (dozing) {
+ mLockIcon.setVisibility(INVISIBLE);
+ } else {
+ mLockIcon.setVisibility(VISIBLE);
+ if (animate) {
+ startFinishDozeAnimation();
+ }
+ }
+ }
+
private class DefaultLeftButton implements IntentButton {
private IconState mIconState = new IconState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 5fb99da..720ca14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -138,8 +138,8 @@
super.onAttachedToWindow();
Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
NAV_BAR_RIGHT);
- Dependency.get(PluginManager.class).addPluginListener(NavBarButtonProvider.ACTION, this,
- NavBarButtonProvider.VERSION, true /* Allow multiple */);
+ Dependency.get(PluginManager.class).addPluginListener(this,
+ NavBarButtonProvider.class, true /* Allow multiple */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5d13289..ad875f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -783,8 +783,8 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
onPluginDisconnected(null); // Create default gesture helper
- Dependency.get(PluginManager.class).addPluginListener(NavGesture.ACTION, this,
- NavGesture.VERSION, false /* Only one */);
+ Dependency.get(PluginManager.class).addPluginListener(this,
+ NavGesture.class, false /* Only one */);
}
@Override
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 6d7ab47..707997d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -1,7 +1,6 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
@@ -226,12 +225,13 @@
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+ int color = StatusBarIconView.NO_COLOR;
boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
if (colorize) {
- v.setImageTintList(ColorStateList.valueOf(
- DarkIconDispatcher.getTint(mTintArea, v, mIconTint)));
+ color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
}
- v.setIconTint(mIconTint);
+ v.setStaticDrawableColor(color);
+ v.setDecorColor(mIconTint);
}
}
}
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 571ae26..dc5f98c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -446,6 +446,7 @@
public boolean useFullTransitionAmount;
public boolean useLinearTransitionAmount;
public boolean translateContent;
+ public int iconColor = StatusBarIconView.NO_COLOR;
@Override
public void applyToView(View view) {
@@ -505,6 +506,7 @@
}
}
icon.setVisibleState(visibleState, animationsAllowed);
+ icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed);
if (animate) {
animateTo(icon, animationProperties);
} else {
@@ -515,6 +517,14 @@
needsCannedAnimation = false;
}
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof StatusBarIconView) {
+ 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 5da3a10..6da9e90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1151,9 +1151,7 @@
.start();
} else if (statusBarState == StatusBarState.KEYGUARD
|| statusBarState == StatusBarState.SHADE_LOCKED) {
- if (!mDozing) {
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
- }
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
mKeyguardBottomArea.setAlpha(1f);
} else {
mKeyguardBottomArea.setVisibility(View.GONE);
@@ -2103,13 +2101,12 @@
private void updateDozingVisibilities(boolean animate) {
if (mDozing) {
mKeyguardStatusBar.setVisibility(View.INVISIBLE);
- mKeyguardBottomArea.setVisibility(View.INVISIBLE);
+ mKeyguardBottomArea.setDozing(mDozing, animate);
} else {
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
mKeyguardStatusBar.setVisibility(View.VISIBLE);
+ mKeyguardBottomArea.setDozing(mDozing, animate);
if (animate) {
animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
- mKeyguardBottomArea.startFinishDozeAnimation();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 457ed6c..ade1b0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -26,7 +26,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
-import android.icu.text.NumberFormat;
import android.os.UserManager;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -55,7 +54,6 @@
import com.android.systemui.qs.TouchAnimator;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.statusbar.SignalClusterView;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
@@ -68,7 +66,7 @@
public class QuickStatusBarHeader extends BaseStatusBarHeader implements
NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
- BatteryStateChangeCallback, SignalCallback {
+ SignalCallback {
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
private ActivityStarter mActivityStarter;
@@ -110,7 +108,6 @@
private boolean mShowFullAlarm;
private float mDateTimeTranslation;
- private TextView mBatteryLevel;
private SparseBooleanArray mRoamingsBySubId = new SparseBooleanArray();
private boolean mIsRoaming;
@@ -161,8 +158,6 @@
mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
mAlarmStatus.setOnClickListener(this);
- mBatteryLevel = (TextView) findViewById(R.id.battery_level);
-
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
mAlwaysShowMultiUserSwitch = res.getBoolean(R.bool.config_alwaysShowMultiUserSwitcher);
@@ -179,7 +174,9 @@
int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
float intensity = colorForeground == Color.WHITE ? 0 : 1;
cluster.onDarkChanged(new Rect(0, 0, 0, 0), intensity, colorForeground);
+
BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
+ battery.forceShowPercent();
int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
battery.setRawColors(colorForeground, colorSecondary);
@@ -443,17 +440,6 @@
}
}
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
- mBatteryLevel.setText(percentage);
- }
-
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- // Don't care.
- }
-
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {
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 9d96921..dd04741 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -712,6 +712,7 @@
private NetworkController mNetworkController;
private KeyguardMonitorImpl mKeyguardMonitor;
private BatteryController mBatteryController;
+ private boolean mPanelExpanded;
private LogMaker mStatusBarStateLog;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private NotificationIconAreaController mNotificationIconAreaController;
@@ -838,7 +839,7 @@
createAndAddWindows();
mSettingsObserver.onChange(false); // set up
- disable(switches[0], switches[6], false /* animate */);
+ mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
topAppWindowChanged(switches[2] != 0);
@@ -1128,7 +1129,7 @@
.replace(R.id.qs_frame, new QSFragment(), QS.TAG)
.commit();
new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class)
- .startListening(QS.ACTION, QS.VERSION);
+ .startListening();
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
@@ -2510,7 +2511,7 @@
* This needs to be called if state used by {@link #adjustDisableFlags} changes.
*/
public void recomputeDisableFlags(boolean animate) {
- disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
+ mCommandQueue.disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
}
protected H createHandler() {
@@ -2679,6 +2680,7 @@
}
public void setPanelExpanded(boolean isExpanded) {
+ mPanelExpanded = isExpanded;
mStatusBarWindowManager.setPanelExpanded(isExpanded);
mVisualStabilityManager.setPanelExpanded(isExpanded);
if (isExpanded && getBarState() != StatusBarState.KEYGUARD) {
@@ -4320,6 +4322,7 @@
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
mScrimController.setDozing(mDozing);
+ mKeyguardIndicationController.setDozing(mDozing);
// Immediately abort the dozing from the doze scrim controller in case of wake-and-unlock
// for pulsing so the Keyguard fade-out animation scrim can take over.
@@ -5005,8 +5008,12 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- mStackScroller.setPulsing(true);
- mVisualStabilityManager.setPulsing(true);
+ if (!mHeadsUpManager.getAllEntries().isEmpty()) {
+ // Only pulse the stack scroller if there's actually something to show.
+ // Otherwise just show the always-on screen.
+ mStackScroller.setPulsing(true);
+ mVisualStabilityManager.setPulsing(true);
+ }
}
@Override
@@ -6090,8 +6097,10 @@
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);
+ entry.cacheContentViews(mContext, null, isLowPriority, useIncreasedCollapsedHeight,
+ useIncreasedHeadsUp);
} catch (RuntimeException e) {
Log.e(TAG, "Unable to get notification remote views", e);
return false;
@@ -6262,6 +6271,7 @@
}
row.setUserLocked(userLocked);
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+ row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
row.onNotificationUpdated(entry);
return true;
}
@@ -6756,10 +6766,13 @@
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);
+ mNotificationData.isAmbient(key), useIncreasedCollapsedHeight,
+ useIncreasedHeadsUp);
} catch (RuntimeException e) {
Log.e(TAG, "Unable to get notification remote views", e);
applyInPlace = false;
@@ -6925,7 +6938,10 @@
return false;
}
- if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
+ // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+ int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+ : NotificationManager.IMPORTANCE_HIGH;
+ if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
return false;
}
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 dd4e876..67cc5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -88,6 +88,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
/**
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
@@ -1836,12 +1837,29 @@
* @return The first child which has visibility unequal to GONE which is currently below the
* given translationY or equal to it.
*/
- private View getFirstChildBelowTranlsationY(float translationY) {
+ private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
- if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ float rowTranslation = child.getTranslationY();
+ if (rowTranslation >= translationY) {
return child;
+ } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
+ List<ExpandableNotificationRow> notificationChildren =
+ row.getNotificationChildren();
+ for (int childIndex = 0; childIndex < notificationChildren.size();
+ childIndex++) {
+ ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
+ if (rowChild.getTranslationY() + rowTranslation >= translationY) {
+ return rowChild;
+ }
+ }
+ }
}
}
return null;
@@ -2500,7 +2518,7 @@
View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
? groupParentWhenDismissed.getTranslationY()
- : view.getTranslationY());
+ : view.getTranslationY(), true /* ignoreChildren */);
}
if (nextView != null) {
nextView.requestAccessibilityFocus();
@@ -2940,7 +2958,17 @@
AnimationEvent event = new AnimationEvent(child, animationType);
// we need to know the view after this one
- event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
+ float removedTranslation = child.getTranslationY();
+ boolean ignoreChildren = true;
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
+ removedTranslation = row.getTranslationWhenRemoved();
+ ignoreChildren = false;
+ }
+ }
+ event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
+ ignoreChildren);
mAnimationEvents.add(event);
mSwipedOutViews.remove(child);
}
@@ -4004,6 +4032,9 @@
}
public void setPulsing(boolean pulsing) {
+ if (mPulsing == pulsing) {
+ return;
+ }
mPulsing = pulsing;
updateNotificationAnimationStates();
updateContentHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 55085e5..9893434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -389,10 +389,25 @@
// upwards by default
float translationDirection = -1.0f;
if (viewState != null) {
+ float ownPosition = changingView.getTranslationY();
+ if (changingView instanceof ExpandableNotificationRow
+ && event.viewAfterChangingView instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow changingRow =
+ (ExpandableNotificationRow) changingView;
+ ExpandableNotificationRow nextRow =
+ (ExpandableNotificationRow) event.viewAfterChangingView;
+ if (changingRow.isRemoved()
+ && changingRow.wasChildInGroupWhenRemoved()
+ && !nextRow.isChildInGroup()) {
+ // the next row isn't actually a child from a group! Let's
+ // compare absolute positions!
+ ownPosition = changingRow.getTranslationWhenRemoved();
+ }
+ }
// there was a view after this one, Approximate the distance the next child
// travelled
translationDirection = ((viewState.yTranslation
- - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
+ - (ownPosition + actualHeight / 2.0f)) * 2 /
actualHeight);
translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
index 3058c0a..1df12ac 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
@@ -25,7 +25,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarIconController;
-import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
+import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
public class BatteryPreference extends DropDownPreference implements TunerService.Tunable {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java
new file mode 100644
index 0000000..e50fd5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/CustomListPreference.java
@@ -0,0 +1,173 @@
+/*
+ * 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.tuner;
+
+import android.annotation.Nullable;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.support.v14.preference.ListPreferenceDialogFragment;
+import android.support.v7.preference.ListPreference;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class CustomListPreference extends ListPreference {
+
+ public CustomListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomListPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected Dialog onDialogCreated(DialogFragment fragment, Dialog dialog) {
+ return dialog;
+ }
+
+ protected boolean isAutoClosePreference() {
+ return true;
+ }
+
+ /**
+ * Called when a user is about to choose the given value, to determine if we
+ * should show a confirmation dialog.
+ *
+ * @param value the value the user is about to choose
+ * @return the message to show in a confirmation dialog, or {@code null} to
+ * not request confirmation
+ */
+ protected CharSequence getConfirmationMessage(String value) {
+ return null;
+ }
+
+ protected void onDialogStateRestored(DialogFragment fragment, Dialog dialog,
+ Bundle savedInstanceState) {
+ }
+
+ public static class CustomListPreferenceDialogFragment extends ListPreferenceDialogFragment {
+
+ private static final String KEY_CLICKED_ENTRY_INDEX
+ = "settings.CustomListPrefDialog.KEY_CLICKED_ENTRY_INDEX";
+
+ private int mClickedDialogEntryIndex;
+
+ public static ListPreferenceDialogFragment newInstance(String key) {
+ final ListPreferenceDialogFragment fragment = new CustomListPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ public CustomListPreference getCustomizablePreference() {
+ return (CustomListPreference) getPreference();
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ mClickedDialogEntryIndex = getCustomizablePreference()
+ .findIndexOfValue(getCustomizablePreference().getValue());
+ getCustomizablePreference().onPrepareDialogBuilder(builder, getOnItemClickListener());
+ if (!getCustomizablePreference().isAutoClosePreference()) {
+ builder.setPositiveButton(com.android.internal.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ onItemConfirmed();
+ }
+ });
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ if (savedInstanceState != null) {
+ mClickedDialogEntryIndex = savedInstanceState.getInt(KEY_CLICKED_ENTRY_INDEX,
+ mClickedDialogEntryIndex);
+ }
+ return getCustomizablePreference().onDialogCreated(this, dialog);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_CLICKED_ENTRY_INDEX, mClickedDialogEntryIndex);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ getCustomizablePreference().onDialogStateRestored(this, getDialog(), savedInstanceState);
+ }
+
+ protected OnClickListener getOnItemClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setClickedDialogEntryIndex(which);
+ if (getCustomizablePreference().isAutoClosePreference()) {
+ onItemConfirmed();
+ }
+ }
+ };
+ }
+
+ protected void setClickedDialogEntryIndex(int which) {
+ mClickedDialogEntryIndex = which;
+ }
+
+ private String getValue() {
+ final ListPreference preference = getCustomizablePreference();
+ if (mClickedDialogEntryIndex >= 0 && preference.getEntryValues() != null) {
+ return preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+ } else {
+ return null;
+ }
+ }
+
+ protected void onItemConfirmed() {
+ onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
+ getDialog().dismiss();
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ final ListPreference preference = getCustomizablePreference();
+ final String value = getValue();
+ if (positiveResult && value != null) {
+ if (preference.callChangeListener(value)) {
+ preference.setValue(value);
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
index 6f4a3a4..410d3d2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
@@ -80,10 +80,8 @@
mTunerService = Dependency.get(TunerService.class);
mHandler = new Handler();
addPreferencesFromResource(R.xml.lockscreen_settings);
- setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON,
- LOCKSCREEN_LEFT_UNLOCK);
- setupGroup((PreferenceGroup) findPreference(KEY_RIGHT), LOCKSCREEN_RIGHT_BUTTON,
- LOCKSCREEN_RIGHT_UNLOCK);
+ setupGroup(LOCKSCREEN_LEFT_BUTTON, LOCKSCREEN_LEFT_UNLOCK);
+ setupGroup(LOCKSCREEN_RIGHT_BUTTON, LOCKSCREEN_RIGHT_UNLOCK);
}
@Override
@@ -92,30 +90,14 @@
mTunables.forEach(t -> mTunerService.removeTunable(t));
}
- private void setupGroup(PreferenceGroup group, String buttonSetting, String unlockKey) {
- SwitchPreference customize = (SwitchPreference) group.findPreference(KEY_CUSTOMIZE);
- Preference shortcut = group.findPreference(KEY_SHORTCUT);
- SwitchPreference unlock = (SwitchPreference) group.findPreference(unlockKey);
+ private void setupGroup(String buttonSetting, String unlockKey) {
+ Preference shortcut = findPreference(buttonSetting);
+ SwitchPreference unlock = (SwitchPreference) findPreference(unlockKey);
addTunable((k, v) -> {
- boolean visible = v != null;
- customize.setChecked(visible);
- shortcut.setVisible(visible);
+ boolean visible = !TextUtils.isEmpty(v);
unlock.setVisible(visible);
- if (visible) {
- setSummary(shortcut, v);
- }
+ setSummary(shortcut, v);
}, buttonSetting);
- customize.setOnPreferenceChangeListener((preference, newValue) -> {
- boolean hasSetting = mTunerService.getValue(buttonSetting) != null;
- if (hasSetting != (boolean) newValue) {
- mHandler.post(() -> mTunerService.setValue(buttonSetting, hasSetting ? null : ""));
- }
- return true;
- });
- shortcut.setOnPreferenceClickListener(preference -> {
- showSelectDialog(buttonSetting);
- return true;
- });
}
private void showSelectDialog(String buttonSetting) {
@@ -129,24 +111,15 @@
mTunerService.setValue(buttonSetting, item.getSettingValue());
dialog.dismiss();
});
- LauncherApps apps = getContext().getSystemService(LauncherApps.class);
- List<LauncherActivityInfo> activities = apps.getActivityList(null,
- Process.myUserHandle());
-
- activities.forEach(info -> {
- App app = new App(getContext(), info);
- try {
- new ShortcutParser(getContext(), info.getComponentName()).getShortcuts().forEach(
- shortcut -> app.addChild(new StaticShortcut(getContext(), shortcut)));
- } catch (NameNotFoundException e) {
- }
- adapter.addItem(app);
- });
v.setAdapter(adapter);
}
private void setSummary(Preference shortcut, String value) {
+ if (value == null) {
+ shortcut.setSummary(R.string.lockscreen_none);
+ return;
+ }
if (value.contains("::")) {
Shortcut info = getShortcutInfo(getContext(), value);
shortcut.setSummary(info != null ? info.label : null);
@@ -155,7 +128,7 @@
shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager())
: null);
} else {
- shortcut.setSummary(null);
+ shortcut.setSummary(R.string.lockscreen_none);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 28a0057..45abd45 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -33,6 +33,9 @@
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -58,7 +61,7 @@
import java.util.ArrayList;
-public class NavBarTuner extends PreferenceFragment {
+public class NavBarTuner extends TunerPreferenceFragment {
private static final String LAYOUT = "layout";
private static final String LEFT = "left";
@@ -68,13 +71,13 @@
private static final String KEYCODE = "keycode";
private static final String ICON = "icon";
- private static final int[] ICONS = new int[]{
- R.drawable.ic_qs_circle,
- R.drawable.ic_add,
- R.drawable.ic_remove,
- R.drawable.ic_left,
- R.drawable.ic_right,
- R.drawable.ic_menu,
+ private static final int[][] ICONS = new int[][]{
+ {R.drawable.ic_qs_circle, R.string.tuner_circle},
+ {R.drawable.ic_add, R.string.tuner_plus},
+ {R.drawable.ic_remove, R.string.tuner_minus},
+ {R.drawable.ic_left, R.string.tuner_left},
+ {R.drawable.ic_right, R.string.tuner_right},
+ {R.drawable.ic_menu, R.string.tuner_menu},
};
private final ArrayList<Tunable> mTunables = new ArrayList<>();
@@ -96,10 +99,8 @@
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.nav_bar_tuner);
bindLayout((ListPreference) findPreference(LAYOUT));
- bindButton((PreferenceCategory) findPreference(LEFT),
- NAV_BAR_LEFT, NAVSPACE);
- bindButton((PreferenceCategory) findPreference(RIGHT),
- NAV_BAR_RIGHT, MENU_IME);
+ bindButton(NAV_BAR_LEFT, NAVSPACE, LEFT);
+ bindButton(NAV_BAR_RIGHT, MENU_IME, RIGHT);
}
@Override
@@ -129,9 +130,8 @@
});
}
- private void bindButton(PreferenceCategory parent, String setting, String def) {
- String k = parent.getKey();
- DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k);
+ private void bindButton(String setting, String def, String k) {
+ ListPreference type = (ListPreference) findPreference(TYPE + "_" + k);
Preference keycode = findPreference(KEYCODE + "_" + k);
ListPreference icon = (ListPreference) findPreference(ICON + "_" + k);
setupIcons(icon);
@@ -195,8 +195,14 @@
.loadDrawable(getContext());
d.setTint(Color.BLACK);
d.setBounds(0, 0, size, size);
- ImageSpan span = new ImageSpan(d);
+ ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
builder.append(" ", span, 0);
+ builder.append(" ");
+ for (int i = 0; i < ICONS.length; i++) {
+ if (ICONS[i][0] == id) {
+ builder.append(getString(ICONS[i][1]));
+ }
+ }
icon.setSummary(builder);
} catch (Exception e) {
Log.d("NavButton", "Problem with summary", e);
@@ -204,7 +210,7 @@
}
}
- private void setValue(String setting, DropDownPreference type, Preference keycode,
+ private void setValue(String setting, ListPreference type, Preference keycode,
ListPreference icon) {
String button = type.getValue();
if (KEY.equals(button)) {
@@ -226,14 +232,16 @@
getContext().getResources().getDisplayMetrics());
for (int i = 0; i < ICONS.length; i++) {
SpannableStringBuilder builder = new SpannableStringBuilder();
- Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i])
+ Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i][0])
.loadDrawable(getContext());
d.setTint(Color.BLACK);
d.setBounds(0, 0, size, size);
- ImageSpan span = new ImageSpan(d);
+ ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
builder.append(" ", span, 0);
+ builder.append(" ");
+ builder.append(getString(ICONS[i][1]));
labels[i] = builder;
- values[i] = getContext().getPackageName() + "/" + ICONS[i];
+ values[i] = getContext().getPackageName() + "/" + ICONS[i][0];
}
icon.setEntries(labels);
icon.setEntryValues(values);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
new file mode 100644
index 0000000..dc0d8bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
@@ -0,0 +1,145 @@
+/*
+ * 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.tuner;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toolbar;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.R;
+
+import libcore.util.Objects;
+
+public class RadioListPreference extends CustomListPreference {
+
+ private OnClickListener mOnClickListener;
+ private CharSequence mSummary;
+
+ public RadioListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder, OnClickListener listener) {
+ mOnClickListener = listener;
+ }
+
+ @Override
+ public void setSummary(CharSequence summary) {
+ super.setSummary(summary);
+ mSummary = summary;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ if (mSummary == null || mSummary.toString().contains("%s")) {
+ return super.getSummary();
+ }
+ return mSummary;
+ }
+
+ @Override
+ protected Dialog onDialogCreated(DialogFragment fragment, Dialog dialog) {
+ Dialog d = new Dialog(getContext(), android.R.style.Theme_DeviceDefault_Settings);
+ Toolbar t = (Toolbar) d.findViewById(com.android.internal.R.id.action_bar);
+ View v = new View(getContext());
+ v.setId(R.id.content);
+ d.setContentView(v);
+ t.setTitle(getTitle());
+ t.setNavigationIcon(Utils.getDrawable(d.getContext(), android.R.attr.homeAsUpIndicator));
+ t.setNavigationOnClickListener(view -> d.dismiss());
+
+ RadioFragment f = new RadioFragment();
+ f.setPreference(this);
+ FragmentHostManager.get(v).getFragmentManager()
+ .beginTransaction()
+ .add(android.R.id.content, f)
+ .commit();
+ return d;
+ }
+
+ @Override
+ protected void onDialogStateRestored(DialogFragment fragment, Dialog dialog,
+ Bundle savedInstanceState) {
+ super.onDialogStateRestored(fragment, dialog, savedInstanceState);
+ View view = dialog.findViewById(R.id.content);
+ RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view)
+ .getFragmentManager().findFragmentById(R.id.content);
+ if (radioFragment != null) {
+ radioFragment.setPreference(this);
+ }
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ }
+
+ public static class RadioFragment extends TunerPreferenceFragment {
+ private RadioListPreference mListPref;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ Context context = getPreferenceManager().getContext();
+ PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context);
+ setPreferenceScreen(screen);
+ if (mListPref != null) {
+ update();
+ }
+ }
+
+ private void update() {
+ Context context = getPreferenceManager().getContext();
+
+ CharSequence[] entries = mListPref.getEntries();
+ CharSequence[] values = mListPref.getEntryValues();
+ CharSequence current = mListPref.getValue();
+ for (int i = 0; i < entries.length; i++) {
+ CharSequence entry = entries[i];
+ SelectablePreference pref = new SelectablePreference(context);
+ getPreferenceScreen().addPreference(pref);
+ pref.setTitle(entry);
+ pref.setChecked(Objects.equal(current, values[i]));
+ pref.setKey(String.valueOf(i));
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ mListPref.mOnClickListener.onClick(null, Integer.parseInt(preference.getKey()));
+ return true;
+ }
+
+ public void setPreference(RadioListPreference radioListPreference) {
+ mListPref = radioListPreference;
+ if (getPreferenceManager() != null) {
+ update();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java
new file mode 100644
index 0000000..1d15708
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/SelectablePreference.java
@@ -0,0 +1,45 @@
+/*
+ * 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.tuner;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v7.preference.CheckBoxPreference;
+import android.util.TypedValue;
+
+import com.android.systemui.statusbar.ScalingDrawableWrapper;
+
+public class SelectablePreference extends CheckBoxPreference {
+ private final int mSize;
+
+ public SelectablePreference(Context context) {
+ super(context);
+ setWidgetLayoutResource(com.android.systemui.R.layout.preference_widget_radiobutton);
+ setSelectable(true);
+ mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32,
+ context.getResources().getDisplayMetrics());
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ super.setIcon(new ScalingDrawableWrapper(icon,
+ mSize / (float) icon.getIntrinsicWidth()));
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java
new file mode 100644
index 0000000..533388a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutPicker.java
@@ -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.
+ */
+
+package com.android.systemui.tuner;
+
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.Process;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.PreferenceViewHolder;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.tuner.ShortcutParser.Shortcut;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShortcutPicker extends PreferenceFragment implements Tunable {
+
+ private final ArrayList<SelectablePreference> mSelectablePreferences = new ArrayList<>();
+ private String mKey;
+ private SelectablePreference mNonePreference;
+ private TunerService mTunerService;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ Context context = getPreferenceManager().getContext();
+ PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context);
+ screen.setOrderingAsAdded(true);
+ PreferenceCategory otherApps = new PreferenceCategory(context);
+ otherApps.setTitle(R.string.tuner_other_apps);
+
+ mNonePreference = new SelectablePreference(context);
+ mSelectablePreferences.add(mNonePreference);
+ mNonePreference.setTitle(R.string.lockscreen_none);
+ mNonePreference.setIcon(R.drawable.ic_remove_circle);
+ screen.addPreference(mNonePreference);
+
+ LauncherApps apps = getContext().getSystemService(LauncherApps.class);
+ List<LauncherActivityInfo> activities = apps.getActivityList(null,
+ Process.myUserHandle());
+
+ screen.addPreference(otherApps);
+ activities.forEach(info -> {
+ try {
+ List<Shortcut> shortcuts = new ShortcutParser(getContext(),
+ info.getComponentName()).getShortcuts();
+ AppPreference appPreference = new AppPreference(context, info);
+ mSelectablePreferences.add(appPreference);
+ if (shortcuts.size() != 0) {
+ //PreferenceCategory category = new PreferenceCategory(context);
+ //screen.addPreference(category);
+ //category.setTitle(info.getLabel());
+ screen.addPreference(appPreference);
+ shortcuts.forEach(shortcut -> {
+ ShortcutPreference shortcutPref = new ShortcutPreference(context, shortcut,
+ info.getLabel());
+ mSelectablePreferences.add(shortcutPref);
+ screen.addPreference(shortcutPref);
+ });
+ return;
+ }
+ otherApps.addPreference(appPreference);
+ } catch (NameNotFoundException e) {
+ }
+ });
+ // Move other apps to the bottom.
+ screen.removePreference(otherApps);
+ for (int i = 0; i < otherApps.getPreferenceCount(); i++) {
+ Preference p = otherApps.getPreference(0);
+ otherApps.removePreference(p);
+ p.setOrder(Preference.DEFAULT_ORDER);
+ screen.addPreference(p);
+ }
+ //screen.addPreference(otherApps);
+
+ setPreferenceScreen(screen);
+ mKey = getArguments().getString(ARG_PREFERENCE_ROOT);
+ mTunerService = Dependency.get(TunerService.class);
+ mTunerService.addTunable(this, mKey);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ mTunerService.setValue(mKey, preference.toString());
+ getActivity().onBackPressed();
+ return true;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (LOCKSCREEN_LEFT_BUTTON.equals(mKey)) {
+ getActivity().setTitle(R.string.lockscreen_shortcut_left);
+ } else {
+ getActivity().setTitle(R.string.lockscreen_shortcut_right);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTunerService.removeTunable(this);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ String v = newValue != null ? newValue : "";
+ mSelectablePreferences.forEach(p -> p.setChecked(v.equals(p.toString())));
+ }
+
+ private static class AppPreference extends SelectablePreference {
+ private final LauncherActivityInfo mInfo;
+ private boolean mBinding;
+
+ public AppPreference(Context context, LauncherActivityInfo info) {
+ super(context);
+ mInfo = info;
+ setTitle(context.getString(R.string.tuner_launch_app, info.getLabel()));
+ setSummary(context.getString(R.string.tuner_app, info.getLabel()));
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ mBinding = true;
+ if (getIcon() == null) {
+ setIcon(mInfo.getBadgedIcon(
+ getContext().getResources().getConfiguration().densityDpi));
+ }
+ mBinding = false;
+ super.onBindViewHolder(holder);
+ }
+
+ @Override
+ protected void notifyChanged() {
+ if (mBinding) return;
+ super.notifyChanged();
+ }
+
+ @Override
+ public String toString() {
+ return mInfo.getComponentName().flattenToString();
+ }
+ }
+
+ private static class ShortcutPreference extends SelectablePreference {
+ private final Shortcut mShortcut;
+ private boolean mBinding;
+
+ public ShortcutPreference(Context context, Shortcut shortcut, CharSequence appLabel) {
+ super(context);
+ mShortcut = shortcut;
+ setTitle(shortcut.label);
+ setSummary(context.getString(R.string.tuner_app, appLabel));
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ mBinding = true;
+ if (getIcon() == null) {
+ setIcon(mShortcut.icon.loadDrawable(getContext()));
+ }
+ mBinding = false;
+ super.onBindViewHolder(holder);
+ }
+
+ @Override
+ protected void notifyChanged() {
+ if (mBinding) return;
+ super.notifyChanged();
+ }
+
+ @Override
+ public String toString() {
+ return mShortcut.toString();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 74280a3..4eb1db6 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -22,10 +22,12 @@
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.util.Log;
+import android.view.MenuItem;
import com.android.settingslib.drawer.SettingsDrawerActivity;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.fragments.FragmentService;
public class TunerActivity extends SettingsDrawerActivity implements
PreferenceFragment.OnPreferenceStartFragmentCallback,
@@ -51,6 +53,22 @@
}
@Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Dependency.destroy(FragmentService.class, s -> s.destroyAll());
+ Dependency.clearDependencies();
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
public void onBackPressed() {
if (!getFragmentManager().popBackStackImmediate()) {
super.onBackPressed();
@@ -62,6 +80,9 @@
try {
Class<?> cls = Class.forName(pref.getFragment());
Fragment fragment = (Fragment) cls.newInstance();
+ final Bundle b = new Bundle(1);
+ b.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ fragment.setArguments(b);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
setTitle(pref.getTitle());
transaction.replace(R.id.content_frame, fragment);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java
new file mode 100644
index 0000000..06d40da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerPreferenceFragment.java
@@ -0,0 +1,36 @@
+/*
+ * 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.tuner;
+
+import android.app.DialogFragment;
+import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+public abstract class TunerPreferenceFragment extends PreferenceFragment {
+
+ @Override
+ public void onDisplayPreferenceDialog(Preference preference) {
+ DialogFragment f = null;
+ if (preference instanceof CustomListPreference) {
+ f = CustomListPreference.CustomListPreferenceDialogFragment
+ .newInstance(preference.getKey());
+ } else {
+ super.onDisplayPreferenceDialog(preference);
+ }
+ f.setTargetFragment(this, 0);
+ f.show(getFragmentManager(), "dialog_preference");
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 85be4d7..6a92b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -38,7 +38,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
-import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
+import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index cd465ac..ec5030b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -120,8 +120,7 @@
}
CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity);
- super.onCreate(savedInstanceState, target, title, null, rList,
- true /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ );
+ super.onCreate(savedInstanceState, target, title, null, rList, true);
CheckBox alwaysUse = (CheckBox)findViewById(com.android.internal.R.id.alwaysUse);
if (alwaysUse != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index ef743e3..ba9e60a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -20,6 +20,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.support.annotation.VisibleForTesting;
import com.android.systemui.Dependency;
@@ -80,11 +81,15 @@
public static class Service extends SystemUI {
+ // TODO(b/35345376): Turn this back on for debuggable builds after known leak fixed.
+ private static final boolean ENABLED = Build.IS_DEBUGGABLE
+ && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
+
private GarbageMonitor mGarbageMonitor;
@Override
public void start() {
- if (!Build.IS_DEBUGGABLE) {
+ if (!ENABLED) {
return;
}
mGarbageMonitor = Dependency.get(GarbageMonitor.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 09808d4..6b47ada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import com.android.settingslib.graph.BatteryMeterDrawableBase;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
@@ -41,17 +42,18 @@
public class BatteryMeterDrawableTest extends SysuiTestCase {
private Resources mResources;
- private BatteryMeterDrawable mBatteryMeter;
+ private BatteryMeterDrawableBase mBatteryMeter;
@Before
public void setUp() throws Exception {
mResources = mContext.getResources();
- mBatteryMeter = new BatteryMeterDrawable(mContext, 0);
+ mBatteryMeter = new BatteryMeterDrawableBase(mContext, 0);
}
@Test
public void testDrawImageButNoTextIfPluggedIn() {
- mBatteryMeter.onBatteryLevelChanged(0, true, true);
+ mBatteryMeter.setBatteryLevel(0);
+ mBatteryMeter.setPluggedIn(true);
final Canvas canvas = mock(Canvas.class);
mBatteryMeter.draw(canvas);
verify(canvas, atLeastOnce()).drawPath(any(), any());
@@ -60,7 +62,8 @@
@Test
public void testDrawTextIfNotPluggedIn() {
- mBatteryMeter.onBatteryLevelChanged(0, false, false);
+ mBatteryMeter.setBatteryLevel(0);
+ mBatteryMeter.setPluggedIn(false);
final Canvas canvas = mock(Canvas.class);
mBatteryMeter.draw(canvas);
verify(canvas, times(1)).drawText(anyString(), anyFloat(), anyFloat(), any());
@@ -68,8 +71,9 @@
@Test
public void testDrawNoTextIfPowerSaveEnabled() {
- mBatteryMeter.onBatteryLevelChanged(0, false, false);
- mBatteryMeter.onPowerSaveChanged(true);
+ mBatteryMeter.setBatteryLevel(0);
+ mBatteryMeter.setPluggedIn(false);
+ mBatteryMeter.setPowerSave(true);
final Canvas canvas = mock(Canvas.class);
mBatteryMeter.draw(canvas);
verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any());
@@ -79,7 +83,8 @@
public void testDrawTextWarningAtCriticalLevel() {
int criticalLevel = mResources.getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
- mBatteryMeter.onBatteryLevelChanged(criticalLevel, false, false);
+ mBatteryMeter.setBatteryLevel(criticalLevel);
+ mBatteryMeter.setPluggedIn(false);
final Canvas canvas = mock(Canvas.class);
mBatteryMeter.draw(canvas);
String warningString = mResources.getString(R.string.battery_meter_very_low_overlay_symbol);
@@ -90,7 +95,8 @@
public void testDrawTextNoWarningAboveCriticalLevel() {
int criticalLevel = mResources.getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
- mBatteryMeter.onBatteryLevelChanged(criticalLevel + 1, false, false);
+ mBatteryMeter.setBatteryLevel(criticalLevel + 1);
+ mBatteryMeter.setPluggedIn(false);
final Canvas canvas = mock(Canvas.class);
mBatteryMeter.draw(canvas);
String warningString = mResources.getString(R.string.battery_meter_very_low_overlay_symbol);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index c3948258..32afee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -289,4 +289,12 @@
assertEquals(DOZE_PULSING, mMachine.getState());
}
+
+ @Test
+ @UiThreadTest
+ public void testWakeUp_wakesUp() {
+ mMachine.wakeUp();
+
+ assertTrue(mServiceFake.requestedWakeup);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java
index d12fc2c..c1e7fe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeServiceFake.java
@@ -22,6 +22,7 @@
public boolean finished;
public int screenState;
+ public boolean requestedWakeup;
public DozeServiceFake() {
reset();
@@ -41,4 +42,9 @@
finished = false;
screenState = Display.STATE_UNKNOWN;
}
+
+ @Override
+ public void requestWakeUp() {
+ requestedWakeup = true;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
new file mode 100644
index 0000000..9b868db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.keyguard;
+
+import static android.app.ActivityManager.TaskDescription;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.annotation.ColorInt;
+import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Looper;
+
+import com.android.systemui.keyguard.WorkLockActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WorkLockActivityTest {
+ private static final @UserIdInt int USER_ID = 270;
+ private static final String TASK_LABEL = "task label";
+
+ private @Mock DevicePolicyManager mDevicePolicyManager;
+ private @Mock KeyguardManager mKeyguardManager;
+ private @Mock Context mContext;
+
+ private WorkLockActivity mActivity;
+
+ private static class WorkLockActivityTestable extends WorkLockActivity {
+ WorkLockActivityTestable(Context baseContext) {
+ super();
+ attachBaseContext(baseContext);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getSystemService(eq(Context.DEVICE_POLICY_SERVICE)))
+ .thenReturn(mDevicePolicyManager);
+ when(mContext.getSystemService(eq(Context.KEYGUARD_SERVICE)))
+ .thenReturn(mKeyguardManager);
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mActivity = new WorkLockActivityTestable(mContext);
+ }
+
+ @Test
+ public void testBackgroundAlwaysOpaque() throws Exception {
+ final @ColorInt int orgColor = Color.rgb(250, 199, 67);
+ when(mDevicePolicyManager.getOrganizationColorForUser(eq(USER_ID))).thenReturn(orgColor);
+
+ final @ColorInt int opaqueColor= Color.rgb(164, 198, 57);
+ final @ColorInt int transparentColor = Color.argb(0, 0, 0, 0);
+ TaskDescription opaque = new TaskDescription(null, null, opaqueColor);
+ TaskDescription transparent = new TaskDescription(null, null, transparentColor);
+
+ // When a task description is provided with a suitable (opaque) primaryColor, it should be
+ // used as the scrim's background color.
+ mActivity.setIntent(new Intent()
+ .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, opaque));
+ assertEquals(opaqueColor, mActivity.getPrimaryColor());
+
+ // When a task description is provided but has no primaryColor / the primaryColor is
+ // transparent, the organization color should be used instead.
+ mActivity.setIntent(new Intent()
+ .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, transparent));
+ assertEquals(orgColor, mActivity.getPrimaryColor());
+
+ // When no task description is provided at all, it should be treated like a transparent
+ // description and the organization color shown instead.
+ mActivity.setIntent(new Intent()
+ .putExtra(Intent.EXTRA_USER_ID, USER_ID));
+ assertEquals(orgColor, mActivity.getPrimaryColor());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
index 3715df2..658966c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -17,14 +17,27 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.VersionInfo.InvalidVersionException;
+import com.android.systemui.plugins.annotations.Requires;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
import android.app.Activity;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -35,7 +48,6 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.HandlerThread;
@@ -44,17 +56,6 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -72,6 +73,7 @@
private PluginListener mMockListener;
private PluginInstanceManager mPluginInstanceManager;
private PluginManager mMockManager;
+ private VersionInfo mMockVersionInfo;
@Before
public void setup() throws Exception {
@@ -83,8 +85,10 @@
mMockManager = mock(PluginManager.class);
when(mMockManager.getClassLoader(any(), any()))
.thenReturn(getClass().getClassLoader());
+ mMockVersionInfo = mock(VersionInfo.class);
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true);
+ mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
+ mMockManager, true);
sMockPlugin = mock(Plugin.class);
when(sMockPlugin.getVersion()).thenReturn(1);
}
@@ -145,7 +149,7 @@
NotificationManager nm = mock(NotificationManager.class);
mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm);
setupFakePmQuery();
- when(sMockPlugin.getVersion()).thenReturn(2);
+ doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any());
mPluginInstanceManager.loadAll();
@@ -181,7 +185,8 @@
public void testNonDebuggable() throws Exception {
// Create a version that thinks the build is not debuggable.
mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false);
+ mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
+ mMockManager, false);
setupFakePmQuery();
mPluginInstanceManager.loadAll();
@@ -270,6 +275,9 @@
}
}
+ // This target class doesn't matter, it just needs to have a Requires to hit the flow where
+ // the mock version info is called.
+ @Requires(target = PluginManagerTest.class, version = 1)
public static class TestPlugin implements Plugin {
@Override
public int getVersion() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index a58407b..09ac5a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -32,6 +32,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory;
@@ -63,7 +64,7 @@
mMockFactory = mock(PluginInstanceManagerFactory.class);
mMockPluginInstance = mock(PluginInstanceManager.class);
when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
- Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any()))
+ Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn(mMockPluginInstance);
mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
resetExceptionHandler();
@@ -76,20 +77,20 @@
Plugin mockPlugin = mock(Plugin.class);
when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin,
null));
- Plugin result = mPluginManager.getOneShotPlugin("myAction", 1);
+ Plugin result = mPluginManager.getOneShotPlugin("myAction", TestPlugin.class);
assertTrue(result == mockPlugin);
}
@Test
public void testAddListener() {
- mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
verify(mMockPluginInstance).loadAll();
}
@Test
public void testRemoveListener() {
- mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
mPluginManager.removePluginListener(mMockListener);
verify(mMockPluginInstance).destroy();
@@ -101,16 +102,16 @@
mMockExceptionHandler);
resetExceptionHandler();
- mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
verify(mMockPluginInstance, Mockito.never()).loadAll();
- assertNull(mPluginManager.getOneShotPlugin("myPlugin", 1));
+ assertNull(mPluginManager.getOneShotPlugin("myPlugin", TestPlugin.class));
verify(mMockPluginInstance, Mockito.never()).getPlugin();
}
@Test
public void testExceptionHandler_foundPlugin() {
- mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true);
mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
@@ -125,7 +126,7 @@
@Test
public void testExceptionHandler_noFoundPlugin() {
- mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false);
mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
@@ -161,4 +162,10 @@
// Set back the real exception handler so the test can crash if it wants to.
Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler);
}
+
+ @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
+ public static interface TestPlugin extends Plugin {
+ public static final String ACTION = "testAction";
+ public static final int VERSION = 1;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java
new file mode 100644
index 0000000..0d87d6b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.plugins;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.VersionInfo.InvalidVersionException;
+import com.android.systemui.plugins.annotations.Requires;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QS.Callback;
+import com.android.systemui.plugins.qs.QS.DetailAdapter;
+import com.android.systemui.plugins.qs.QS.HeightListener;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class VersionInfoTest extends SysuiTestCase {
+
+ @Rule
+ public ExpectedException mThrown = ExpectedException.none();
+
+ @Test
+ public void testHasInfo() {
+ VersionInfo info = new VersionInfo();
+ info.addClass(VersionInfoTest.class); // Has no annotations.
+ assertFalse(info.hasVersionInfo());
+
+ info.addClass(OverlayPlugin.class);
+ assertTrue(info.hasVersionInfo());
+ }
+
+ @Test
+ public void testSingleProvides() {
+ VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class);
+ VersionInfo impl = new VersionInfo().addClass(OverlayImpl.class);
+ overlay.checkVersion(impl);
+ }
+
+ @Test
+ public void testIncorrectVersion() {
+ VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class);
+ VersionInfo impl = new VersionInfo().addClass(OverlayImplIncorrectVersion.class);
+ mThrown.expect(InvalidVersionException.class);
+ overlay.checkVersion(impl);
+ }
+
+ @Test
+ public void testMissingRequired() {
+ VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class);
+ VersionInfo impl = new VersionInfo();
+ mThrown.expect(InvalidVersionException.class);
+ overlay.checkVersion(impl);
+ }
+
+ @Test
+ public void testMissingDependencies() {
+ VersionInfo overlay = new VersionInfo().addClass(QS.class);
+ VersionInfo impl = new VersionInfo().addClass(QSImplNoDeps.class);
+ mThrown.expect(InvalidVersionException.class);
+ overlay.checkVersion(impl);
+ }
+
+ @Test
+ public void testHasDependencies() {
+ VersionInfo overlay = new VersionInfo().addClass(QS.class);
+ VersionInfo impl = new VersionInfo().addClass(QSImpl.class);
+ overlay.checkVersion(impl);
+ }
+
+ @Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
+ public static class OverlayImpl {
+ }
+
+ @Requires(target = OverlayPlugin.class, version = 0)
+ public static class OverlayImplIncorrectVersion {
+ }
+
+ @Requires(target = QS.class, version = QS.VERSION)
+ public static class QSImplNoDeps {
+ }
+
+ @Requires(target = QS.class, version = QS.VERSION)
+ @Requires(target = Callback.class, version = Callback.VERSION)
+ @Requires(target = HeightListener.class, version = HeightListener.VERSION)
+ @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION)
+ public static class QSImpl {
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 4146cb81..70c7d3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -18,26 +18,32 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
import android.content.ComponentName;
-import android.content.Context;
import android.os.Looper;
import android.service.quicksettings.Tile;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysUIRunner;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import java.util.ArrayList;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.utils.TestableLooper;
+import com.android.systemui.utils.TestableLooper.RunWithLooper;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+import java.util.ArrayList;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(SysUIRunner.class)
+@RunWithLooper(setAsMainLooper = true)
public class TileServicesTest extends SysuiTestCase {
private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
@@ -46,16 +52,24 @@
@Before
public void setUp() throws Exception {
+ TestableLooper.get(this).setAsMainLooper();
mManagers = new ArrayList<>();
- QSTileHost host = new QSTileHost(mContext, null, null);
+ QSTileHost host = new QSTileHost(mContext, null,
+ mock(StatusBarIconController.class));
mTileService = new TestTileServices(host, Looper.getMainLooper());
}
+ @After
+ public void tearDown() throws Exception {
+ mTileService.getHost().destroy();
+ TestableLooper.get(this).processAllMessages();
+ }
+
@Test
public void testRecalculateBindAllowance() {
// Add some fake tiles.
for (int i = 0; i < NUM_FAKES; i++) {
- mTileService.getTileWrapper(Mockito.mock(CustomTile.class));
+ mTileService.getTileWrapper(mock(CustomTile.class));
}
assertEquals(NUM_FAKES, mManagers.size());
@@ -91,7 +105,7 @@
@Test
public void testCalcFew() {
for (int i = 0; i < TileServices.DEFAULT_MAX_BOUND - 1; i++) {
- mTileService.getTileWrapper(Mockito.mock(CustomTile.class));
+ mTileService.getTileWrapper(mock(CustomTile.class));
}
mTileService.recalculateBindAllowance();
@@ -115,7 +129,7 @@
@Override
protected TileServiceManager onCreateTileService(ComponentName component, Tile qsTile) {
- TileServiceManager manager = Mockito.mock(TileServiceManager.class);
+ TileServiceManager manager = mock(TileServiceManager.class);
mManagers.add(manager);
return manager;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
index 96a9bee..1194849 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
@@ -30,6 +30,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,18 +72,21 @@
mLeakDir.delete();
}
+ @Ignore("slow")
@Test
public void testDump_postsNotification() {
mLeakReporter.dumpLeak(5);
verify(mNotificationManager).notify(any(), anyInt(), any());
}
+ @Ignore("slow")
@Test
public void testDump_Repeated() {
mLeakReporter.dumpLeak(1);
mLeakReporter.dumpLeak(2);
}
+ @Ignore("slow")
@Test
public void testDump_ProducesNonZeroFiles() {
mLeakReporter.dumpLeak(5);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d1abcca..59a9361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -31,13 +31,7 @@
@Override
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- int version) {
- mLeakChecker.addCallback(listener);
- }
-
- @Override
- public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- int version, boolean allowMultiple) {
+ Class cls, boolean allowMultiple) {
mLeakChecker.addCallback(listener);
}
@@ -47,7 +41,7 @@
}
@Override
- public <T extends Plugin> T getOneShotPlugin(String action, int version) {
+ public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
return null;
}
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index e4b53cb..8d2f0c3 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -41,6 +41,9 @@
// The view or control was dismissed.
TYPE_DISMISS = 5;
+
+ // The view or control was updated.
+ TYPE_UPDATE = 6;
}
// Known visual elements: views or controls.
@@ -3391,6 +3394,41 @@
// OPEN: Carrier demo mode password dialog
CARRIER_DEMO_MODE_PASSWORD = 828;
+ // ACTION: Create a Settings shortcut item.
+ ACTION_SETTINGS_CREATE_SHORTCUT = 829;
+
+ // ACTION: A tile in Settings information architecture is clicked
+ ACTION_SETTINGS_TILE_CLICK = 830;
+
+ // OPEN: Notification unsnoozed. CLOSE: Notification snoozed. UPDATE: snoozed notification
+ // updated
+ // CATEGORY: NOTIFICATION
+ // OS: O
+ NOTIFICATION_SNOOZED = 831;
+
+ // Tagged data for NOTIFICATION_SNOOZED. TRUE: snoozed until context, FALSE: snoozed for time.
+ // OS: O
+ NOTIFICATION_SNOOZED_CRITERIA = 832;
+
+ // FIELD - The context (source) from which an action is performed
+ FIELD_CONTEXT = 833;
+
+ // ACTION: Settings advanced button is expanded
+ ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND = 834;
+
+ // ACTION: Logs the number of times the saved network evaluator was used to
+ // recommend a wifi network
+ WIFI_NETWORK_RECOMMENDATION_SAVED_NETWORK_EVALUATOR = 835;
+
+ // ACTION: Logs the number of times the recommended network evaluator was
+ // used to recommend a wifi network
+ WIFI_NETWORK_RECOMMENDATION_RECOMMENDED_NETWORK_EVALUATOR = 836;
+
+ // ACTION: Logs the number of times a recommended network was resulted in a
+ // successful connection
+ // VALUE: true if the connection was successful, false if the connection failed
+ WIFI_NETWORK_RECOMMENDATION_CONNECTION_SUCCESS = 837;
+
// ---- 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/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
index ed0d234..64c6abd 100644
--- a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
+++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
@@ -126,13 +126,13 @@
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (view == mWindowSizeListenerView) {
if (DEBUG) Slog.d(TAG, "onLayoutChange() for mWindowSizeListenerView");
- // mWindowSizeListenerView layout changed, get the size of the display bounds and update
+ // mWindowSizeListenerView layout changed, get the size of the display bounds and updateLocked
// the window.
final Rect displayBounds = new Rect();
view.getBoundsOnScreen(displayBounds);
updateDisplayBounds(displayBounds);
} else if (view == mContentView) {
- // mContentView layout changed, update the window in case its height changed.
+ // mContentView layout changed, updateLocked the window in case its height changed.
if (DEBUG) Slog.d(TAG, "onLayoutChange() for mContentView");
updateHeight();
}
@@ -159,7 +159,7 @@
MeasureSpec.makeMeasureSpec(displayBounds.height(), MeasureSpec.AT_MOST));
int height = mContentView.getMeasuredHeight();
if (height != mLastHeight) {
- if (DEBUG) Slog.d(TAG, "update height=" + height);
+ if (DEBUG) Slog.d(TAG, "updateLocked height=" + height);
mLastHeight = height;
update(height, mLastBounds, displayBounds);
return true;
@@ -170,7 +170,7 @@
private void updateBounds(Rect bounds) {
if (!bounds.equals(mLastBounds)) {
- if (DEBUG) Slog.d(TAG, "update bounds=" + bounds);
+ if (DEBUG) Slog.d(TAG, "updateLocked bounds=" + bounds);
mLastBounds = bounds;
update(mLastHeight, bounds, mLastDisplayBounds);
@@ -179,7 +179,7 @@
private void updateDisplayBounds(Rect displayBounds) {
if (!displayBounds.equals(mLastDisplayBounds)) {
- if (DEBUG) Slog.d(TAG, "update displayBounds=" + displayBounds);
+ if (DEBUG) Slog.d(TAG, "updateLocked displayBounds=" + displayBounds);
mLastDisplayBounds = displayBounds;
if (!updateHeight()) {
@@ -195,7 +195,7 @@
return;
}
- if (DEBUG) Slog.d(TAG, "update height=" + height + ", bounds=" + bounds
+ if (DEBUG) Slog.d(TAG, "updateLocked height=" + height + ", bounds=" + bounds
+ ", displayBounds=" + displayBounds);
final LayoutParams params = createWindowLayoutParams(mAppToken,
@@ -220,7 +220,7 @@
* the bounds is preferred, if it fits. Otherwise, anchor the window on the side with more
* space.
*
- * @param params the params to update
+ * @param params the params to updateLocked
* @param height the requested height of the window
* @param minMargin the minimum margin between the window and the display bounds
* @param bounds the region the window should be anchored to
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index c16a51c..3257812 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -22,14 +22,14 @@
import static com.android.server.autofill.Helper.VERBOSE;
import android.Manifest;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
-import android.app.AppGlobals;
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.net.Uri;
@@ -37,14 +37,11 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.autofill.IAutoFillManagerService;
-import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import android.util.Slog;
@@ -52,11 +49,12 @@
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
+import android.view.autofill.IAutoFillManager;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.os.SomeArgs;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -68,21 +66,15 @@
/**
* Entry point service for auto-fill management.
*
- * <p>This service provides the {@link IAutoFillManagerService} implementation and keeps a list of
+ * <p>This service provides the {@link IAutoFillManager} implementation and keeps a list of
* {@link AutoFillManagerServiceImpl} per user; the real work is done by
* {@link AutoFillManagerServiceImpl} itself.
*/
+// TODO(b/33197203): Handle removing of packages
public final class AutoFillManagerService extends SystemService {
private static final String TAG = "AutoFillManagerService";
- private static final int MSG_START_SESSION = 1;
- private static final int MSG_UPDATE_SESSION = 2;
- private static final int MSG_FINISH_SESSION = 3;
- private static final int MSG_REQUEST_SAVE_FOR_USER = 4;
- private static final int MSG_LIST_SESSIONS = 5;
- private static final int MSG_RESET = 6;
-
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private final Context mContext;
@@ -90,48 +82,6 @@
private final Object mLock = new Object();
- private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
- switch (msg.what) {
- case MSG_START_SESSION: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final int userId = msg.arg1;
- final IBinder activityToken = (IBinder) args.arg1;
- final IBinder appCallback = (IBinder) args.arg2;
- final AutoFillId autoFillId = (AutoFillId) args.arg3;
- final Rect bounds = (Rect) args.arg4;
- final AutoFillValue value = (AutoFillValue) args.arg5;
- handleStartSession(userId, activityToken, appCallback, autoFillId, bounds, value);
- return;
- } case MSG_FINISH_SESSION: {
- handleFinishSession(msg.arg1, (IBinder) msg.obj);
- return;
- } case MSG_REQUEST_SAVE_FOR_USER: {
- handleSaveForUser(msg.arg1);
- return;
- } case MSG_UPDATE_SESSION: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final IBinder activityToken = (IBinder) args.arg1;
- final AutoFillId autoFillId = (AutoFillId) args.arg2;
- final Rect bounds = (Rect) args.arg3;
- final AutoFillValue value = (AutoFillValue) args.arg4;
- final int userId = args.argi5;
- final int flags = args.argi6;
- handleUpdateSession(userId, activityToken, autoFillId, bounds, value, flags);
- return;
- } case MSG_LIST_SESSIONS: {
- handleListForUser(msg.arg1, (IResultReceiver) msg.obj);
- return;
- } case MSG_RESET: {
- handleReset();
- return;
- } default: {
- Slog.w(TAG, "Invalid message: " + msg);
- }
- }
- };
-
- private HandlerCaller mHandlerCaller;
-
/**
* Cache of {@link AutoFillManagerServiceImpl} per user id.
* <p>
@@ -152,11 +102,26 @@
// TODO(b/33197203): set a different max (or disable it) on low-memory devices.
private final LocalLog mRequestsHistory = new LocalLog(100);
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ final String reason = intent.getStringExtra("reason");
+ if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason);
+ mUi.hideAll();
+ }
+ }
+ };
+
public AutoFillManagerService(Context context) {
super(context);
- mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
mContext = context;
mUi = new AutoFillUI(mContext);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(mBroadcastReceiver, filter, null,
+ FgThread.getHandler());
}
@Override
@@ -171,46 +136,30 @@
}
}
- private AutoFillManagerServiceImpl newServiceForUser(int userId) {
- ComponentName serviceComponent = null;
- ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.AUTO_FILL_SERVICE, userId);
- if (!TextUtils.isEmpty(componentName)) {
- try {
- serviceComponent = ComponentName.unflattenFromString(componentName);
- serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0,
- userId);
- } catch (RuntimeException | RemoteException e) {
- Slog.e(TAG, "Bad auto-fill service name " + componentName, e);
- return null;
- }
+ @Override
+ public void onUnlockUser(int userId) {
+ synchronized (mLock) {
+ updateCachedServiceLocked(userId);
}
+ }
- if (serviceInfo == null) {
- return null;
+ @Override
+ public void onStopUser(int userId) {
+ synchronized (mLock) {
+ removeCachedServiceLocked(userId);
}
-
- try {
- return new AutoFillManagerServiceImpl(mContext, mLock, mRequestsHistory,
- userId, serviceComponent, mUi);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Auto-fill service not found: " + serviceComponent, e);
- }
-
- return null;
}
/**
* Gets the service instance for an user.
*
- * @return service instance or {@code null} if user does not have a service set.
+ * @return service instance.
*/
- @Nullable
- AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
+ @NonNull AutoFillManagerServiceImpl getServiceForUserLocked(int userId) {
AutoFillManagerServiceImpl service = mServicesCache.get(userId);
if (service == null) {
- service = newServiceForUser(userId);
+ service = new AutoFillManagerServiceImpl(mContext, mLock,
+ mRequestsHistory, userId, mUi);
mServicesCache.put(userId, service);
}
return service;
@@ -220,81 +169,6 @@
void requestSaveForUser(int userId) {
Slog.i(TAG, "requestSaveForUser(): " + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageI(
- MSG_REQUEST_SAVE_FOR_USER, userId));
- }
-
- // Called by Shell command.
- void listSessions(int userId, IResultReceiver receiver) {
- Slog.i(TAG, "listSessions() for userId " + userId);
- mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(
- mHandlerCaller.obtainMessageIO(MSG_LIST_SESSIONS, userId, receiver));
- }
-
- // Called by Shell command.
- void reset() {
- Slog.i(TAG, "reset()");
- mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_RESET));
- }
-
- /**
- * Removes a cached service for a given user.
- */
- void removeCachedServiceLocked(int userId) {
- final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
- if (service != null) {
- mServicesCache.delete(userId);
- service.destroyLocked();
- }
- }
-
- private void handleStartSession(int userId, IBinder activityToken, IBinder appCallback,
- AutoFillId autoFillId, Rect bounds, AutoFillValue value) {
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
- if (service == null) {
- return;
- }
- service.startSessionLocked(activityToken, appCallback, autoFillId, bounds, value);
- }
- }
-
- private void handleFinishSession(int userId, IBinder activityToken) {
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
- if (service == null) {
- return;
- }
- service.finishSessionLocked(activityToken);
- }
- }
-
- private void handleUpdateSession(int userId, IBinder activityToken, AutoFillId autoFillId,
- Rect bounds, AutoFillValue value, int flags) {
- synchronized (mLock) {
- final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
- if (service == null) {
- return;
- }
-
- service.updateSessionLocked(activityToken, autoFillId, bounds, value, flags);
- }
- }
-
- private IBinder getTopActivityForUser() {
- final List<IBinder> topActivities = LocalServices
- .getService(ActivityManagerInternal.class).getTopVisibleActivities();
- if (DEBUG) Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
- if (topActivities.isEmpty()) {
- Slog.w(TAG, "Could not get top activity");
- return null;
- }
- return topActivities.get(0);
- }
-
- private void handleSaveForUser(int userId) {
final IBinder activityToken = getTopActivityForUser();
if (activityToken != null) {
synchronized (mLock) {
@@ -309,7 +183,10 @@
}
}
- private void handleListForUser(int userId, IResultReceiver receiver) {
+ // Called by Shell command.
+ void listSessions(int userId, IResultReceiver receiver) {
+ Slog.i(TAG, "listSessions() for userId " + userId);
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
final Bundle resultData = new Bundle();
final ArrayList<String> sessions = new ArrayList<>();
@@ -332,7 +209,10 @@
}
}
- private void handleReset() {
+ // Called by Shell command.
+ void reset() {
+ Slog.i(TAG, "reset()");
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
final int size = mServicesCache.size();
for (int i = 0; i < size; i++) {
@@ -342,48 +222,98 @@
}
}
- final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
+ /**
+ * Removes a cached service for a given user.
+ */
+ private void removeCachedServiceLocked(int userId) {
+ final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+ if (service != null) {
+ mServicesCache.delete(userId);
+ service.destroyLocked();
+ }
+ }
+
+ /**
+ * Updates a cached service for a given user.
+ */
+ private void updateCachedServiceLocked(int userId) {
+ AutoFillManagerServiceImpl service = mServicesCache.get(userId);
+ if (service != null) {
+ service.updateLocked();
+ }
+ }
+
+ private IBinder getTopActivityForUser() {
+ final List<IBinder> topActivities = LocalServices
+ .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+ if (DEBUG) Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+ if (topActivities.isEmpty()) {
+ Slog.w(TAG, "Could not get top activity");
+ return null;
+ }
+ return topActivities.get(0);
+ }
+
+ final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
+ @Override
+ public boolean addClient(IAutoFillManagerClient client, int userId) {
+ synchronized (mLock) {
+ return getServiceForUserLocked(userId).addClientLocked(client);
+ }
+ }
+
+ @Override
+ public void setAuthenticationResult(Bundle data, IBinder activityToken, int userId) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ service.setAuthenticationResultLocked(data, activityToken);
+ }
+ }
@Override
public void startSession(IBinder activityToken, IBinder appCallback, AutoFillId autoFillId,
- Rect bounds, AutoFillValue value) throws RemoteException {
+ Rect bounds, AutoFillValue value, int userId) {
// TODO(b/33197203): make sure it's called by resumed / focused activity
- final int userId = UserHandle.getCallingUserId();
if (VERBOSE) {
Slog.v(TAG, "startSession: autoFillId=" + autoFillId + ", bounds=" + bounds
+ ", value=" + value);
}
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = activityToken;
- args.arg2 = appCallback;
- args.arg3 = autoFillId;
- args.arg4 = bounds;
- args.arg5 = value;
-
- mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_START_SESSION,
- userId, 0, args));
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ service.startSessionLocked(activityToken, appCallback, autoFillId, bounds, value);
+ }
}
@Override
public void updateSession(IBinder activityToken, AutoFillId id, Rect bounds,
- AutoFillValue value, int flags) throws RemoteException {
+ AutoFillValue value, int flags, int userId) {
if (DEBUG) {
Slog.d(TAG, "updateSession: flags=" + flags + ", autoFillId=" + id
+ ", bounds=" + bounds + ", value=" + value);
}
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOII(MSG_UPDATE_SESSION,
- activityToken, id, bounds, value, UserHandle.getCallingUserId(), flags));
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = mServicesCache.get(
+ UserHandle.getCallingUserId());
+ if (service != null) {
+ service.updateSessionLocked(activityToken, id, bounds, value, flags);
+ }
+ }
}
@Override
- public void finishSession(IBinder activityToken) throws RemoteException {
+ public void finishSession(IBinder activityToken, int userId) {
if (VERBOSE) Slog.v(TAG, "finishSession(): " + activityToken);
- mHandlerCaller.sendMessage(mHandlerCaller.getHandler().obtainMessage(MSG_FINISH_SESSION,
- UserHandle.getCallingUserId(), 0, activityToken));
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = mServicesCache.get(
+ UserHandle.getCallingUserId());
+ if (service != null) {
+ service.finishSessionLocked(activityToken);
+ }
+ }
}
@Override
@@ -433,7 +363,7 @@
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
synchronized (mLock) {
- removeCachedServiceLocked(userId);
+ updateCachedServiceLocked(userId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 2891518..a697a8e 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -31,43 +31,45 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.IActivityManager;
+import android.app.AppGlobals;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.ICancellationSignal;
import android.os.Looper;
+import android.os.Parcelable;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.provider.Settings;
import android.service.autofill.AutoFillService;
import android.service.autofill.AutoFillServiceInfo;
-import android.service.autofill.FillCallback;
-import android.service.autofill.IAutoFillAppCallback;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
-import android.service.autofill.IFillCallback;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillManager;
import android.view.autofill.AutoFillValue;
-import android.view.autofill.Dataset;
-import android.view.autofill.FillResponse;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
-import com.android.server.FgThread;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -86,26 +88,14 @@
private static final int MSG_SERVICE_SAVE = 1;
private final int mUserId;
- private final ComponentName mComponent;
- private final String mComponentName;
private final Context mContext;
- private final IActivityManager mAm;
private final Object mLock;
- private final AutoFillServiceInfo mInfo;
private final AutoFillUI mUi;
- private final LocalLog mRequestsHistory;
+ private RemoteCallbackList<IAutoFillManagerClient> mClients;
+ private AutoFillServiceInfo mInfo;
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- final String reason = intent.getStringExtra("reason");
- if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason);
- mUi.hideAll();
- }
- }
- };
+ private final LocalLog mRequestsHistory;
private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
switch (msg.what) {
@@ -119,6 +109,7 @@
private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
mHandlerCallback, true);
+
/**
* Cache of pending {@link Session}s, keyed by {@code activityToken}.
*
@@ -139,7 +130,6 @@
if (DEBUG) Slog.d(TAG, "resultCode on mAssistReceiver: " + resultCode);
final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
-
if (structure == null) {
Slog.w(TAG, "no assist structure for id " + resultCode);
return;
@@ -183,28 +173,78 @@
};
AutoFillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
- int userId, ComponentName component, AutoFillUI ui)
- throws PackageManager.NameNotFoundException {
+ int userId, AutoFillUI ui) {
mContext = context;
mLock = lock;
mRequestsHistory = requestsHistory;
mUserId = userId;
- mComponent = component;
- mComponentName = mComponent.flattenToShortString();
- mAm = ActivityManager.getService();
mUi = ui;
- mInfo = new AutoFillServiceInfo(context.getPackageManager(), component, mUserId);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
+ updateLocked();
}
+ CharSequence getServiceName() {
+ if (mInfo == null) {
+ return null;
+ }
+ final ComponentName serviceComponent = mInfo.getServiceInfo().getComponentName();
+ final String packageName = serviceComponent.getPackageName();
+
+ try {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return pm.getApplicationLabel(info);
+ } catch (Exception e) {
+ Slog.w(TAG, "Could not get label for " + packageName + ": " + e);
+ return packageName;
+ }
+ }
+
+ void updateLocked() {
+ ComponentName serviceComponent = null;
+ ServiceInfo serviceInfo = null;
+ final String componentName = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.AUTO_FILL_SERVICE, mUserId);
+ if (!TextUtils.isEmpty(componentName)) {
+ try {
+ serviceComponent = ComponentName.unflattenFromString(componentName);
+ serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ 0, mUserId);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.e(TAG, "Bad auto-fill service name " + componentName, e);
+ return;
+ }
+ }
+ try {
+ final boolean hadService = hasService();
+ if (serviceInfo != null) {
+ mInfo = new AutoFillServiceInfo(mContext.getPackageManager(),
+ serviceComponent, mUserId);
+ } else {
+ mInfo = null;
+ }
+ if (hadService != hasService()) {
+ if (!hasService()) {
+ final int sessionCount = mSessions.size();
+ for (int i = sessionCount - 1; i >= 0; i--) {
+ Session session = mSessions.valueAt(i);
+ session.destroyLocked();
+ mSessions.removeAt(i);
+ }
+ }
+ sendStateToClients();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Bad auto-fill service name " + componentName, e);
+ }
+ }
/**
* Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app.
*/
void requestSaveForUserLocked(IBinder activityToken) {
+ if (!hasService()) {
+ return;
+ }
final Session session = mSessions.get(activityToken);
if (session == null) {
Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
@@ -214,9 +254,32 @@
session.callSaveLocked();
}
+ boolean addClientLocked(IAutoFillManagerClient client) {
+ if (mClients == null) {
+ mClients = new RemoteCallbackList<>();
+ }
+ mClients.register(client);
+ return hasService();
+ }
+
+ void setAuthenticationResultLocked(Bundle data, IBinder activityToken) {
+ if (!hasService()) {
+ return;
+ }
+ final Session session = mSessions.get(activityToken);
+ if (session != null) {
+ session.setAuthenticationResultLocked(data);
+ }
+ }
+
void startSessionLocked(IBinder activityToken, IBinder appCallbackToken, AutoFillId autoFillId,
Rect bounds, AutoFillValue value) {
- final String historyItem = "s=" + mComponentName + " u=" + mUserId + " a=" + activityToken
+ if (!hasService()) {
+ return;
+ }
+
+ final String historyItem = "s=" + new ComponentName(mInfo.getServiceInfo().packageName,
+ mInfo.getServiceInfo().name) + " u=" + mUserId + " a=" + activityToken
+ " i=" + autoFillId + " b=" + bounds + " v=" + value;
mRequestsHistory.log(historyItem);
@@ -229,24 +292,23 @@
final Session newSession = createSessionByTokenLocked(activityToken, appCallbackToken);
newSession.updateLocked(autoFillId, bounds, value, FLAG_START_SESSION);
- newSession.enableSessionLocked();
}
void finishSessionLocked(IBinder activityToken) {
- if (DEBUG) Slog.d(TAG, "finishSessionLocked(): " + activityToken);
- final Session session = mSessions.get(activityToken);
+ if (!hasService()) {
+ return;
+ }
+ final Session session = mSessions.get(activityToken);
if (session == null) {
Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken);
return;
}
- mUi.hideFillUi();
session.showSaveLocked();
}
private Session createSessionByTokenLocked(IBinder activityToken, IBinder appCallbackToken) {
-
final Session newSession = new Session(mContext, activityToken, appCallbackToken);
mSessions.put(activityToken, newSession);
@@ -261,10 +323,16 @@
// TODO(b/33197203): add MetricsLogger call
final Bundle receiverExtras = new Bundle();
receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken);
- if (!mAm.requestAutoFillData(mAssistReceiver, receiverExtras, activityToken)) {
- // TODO(b/33197203): might need a way to warn user (perhaps a new method on
- // AutoFillService).
- Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!ActivityManager.getService().requestAutoFillData(mAssistReceiver,
+ receiverExtras, activityToken)) {
+ // TODO(b/33197203): might need a way to warn user (perhaps a new method on
+ // AutoFillService).
+ Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
} catch (RemoteException e) {
// Should not happen, it's a local call.
@@ -274,7 +342,6 @@
void updateSessionLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
AutoFillValue value, int flags) {
-
// TODO(b/33197203): add MetricsLogger call
final Session session = mSessions.get(activityToken);
if (session == null) {
@@ -286,7 +353,6 @@
}
private void handleSessionSave(IBinder activityToken) {
-
synchronized (mLock) {
final Session session = mSessions.get(activityToken);
if (session == null) {
@@ -301,7 +367,6 @@
void destroyLocked() {
if (VERBOSE) Slog.v(TAG, "destroyLocked()");
- mContext.unregisterReceiver(mBroadcastReceiver);
for (Session session : mSessions.values()) {
session.destroyLocked();
}
@@ -311,7 +376,8 @@
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
- pw.print(prefix); pw.println("Component:"); pw.println(mComponentName);
+ pw.print(prefix); pw.println("Component:"); pw.println(mInfo != null
+ ? mInfo.getServiceInfo().getComponentName() : null);
if (VERBOSE) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
@@ -333,14 +399,44 @@
void listSessionsLocked(ArrayList<String> output) {
for (IBinder activityToken : mSessions.keySet()) {
- output.add(mComponentName + ":" + activityToken);
+ output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
+ : null) + ":" + activityToken);
}
}
+ private void sendStateToClients() {
+ final RemoteCallbackList<IAutoFillManagerClient> clients;
+ final int userClientCount;
+ synchronized (mLock) {
+ if (mClients == null) {
+ return;
+ }
+ clients = mClients;
+ userClientCount = clients.beginBroadcast();
+ }
+ try {
+ for (int i = 0; i < userClientCount; i++) {
+ IAutoFillManagerClient client = clients.getBroadcastItem(i);
+ try {
+ client.setState(hasService());
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ } finally {
+ clients.finishBroadcast();
+ }
+ }
+
+ private boolean hasService() {
+ return mInfo != null;
+ }
+
@Override
public String toString() {
return "AutoFillManagerServiceImpl: [userId=" + mUserId
- + ", component=" + mComponentName + "]";
+ + ", component=" + (mInfo != null
+ ? mInfo.getServiceInfo().getComponentName() : null) + "]";
}
/**
@@ -360,14 +456,20 @@
final AutoFillId mId;
private final Listener mListener;
- // // TODO(b/33197203): does it really need a reference to the session's response?
- private FillResponse mResponse;
+ // TODO(b/33197203): would not need a reference to response and session if it was an inner
+ // class of Session...
+ private final Session mSession;
+ // TODO(b/33197203): encapsulate access so it's not called by UI
+ FillResponse mResponse;
+ Intent mAuthIntent;
+
private AutoFillValue mAutoFillValue;
private Rect mBounds;
private boolean mValueUpdated;
- ViewState(AutoFillId id, Listener listener) {
+ ViewState(Session session, AutoFillId id, Listener listener) {
+ mSession = session;
mId = id;
mListener = listener;
}
@@ -380,6 +482,18 @@
maybeCallOnFillReady();
}
+ /**
+ * Used when a {@link FillResponse} requires authentication to be unlocked.
+ */
+ void setResponse(FillResponse response, Intent authIntent) {
+ mAuthIntent = authIntent;
+ setResponse(response);
+ }
+
+ CharSequence getServiceName() {
+ return mSession.getServiceName();
+ }
+
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that
// it can change the value and update the UI; similarly, should replace code that
// directly sets mAutoFilLValue to use encapsulation.
@@ -417,8 +531,8 @@
pw.print(prefix); pw.print("value:" ); pw.println(mAutoFillValue);
pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+ pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
}
-
}
/**
@@ -448,7 +562,7 @@
@Nullable
private ViewState mCurrentViewState;
- private final IAutoFillAppCallback mAppCallback;
+ private final IAutoFillManagerClient mClient;
@GuardedBy("mLock")
RemoteFillService mRemoteFillService;
@@ -471,23 +585,23 @@
@GuardedBy("mLock")
private AssistStructure mStructure;
- private Session(Context context, IBinder activityToken, IBinder appCallback) {
- mRemoteFillService = new RemoteFillService(context, mComponent, mUserId, this);
+ private Session(Context context, IBinder activityToken, IBinder client) {
+ mRemoteFillService = new RemoteFillService(context,
+ mInfo.getServiceInfo().getComponentName(), mUserId, this);
mActivityToken = activityToken;
- mAppCallback = IAutoFillAppCallback.Stub.asInterface(appCallback);
+ mClient = IAutoFillManagerClient.Stub.asInterface(client);
try {
- appCallback.linkToDeath(() -> {
+ client.linkToDeath(() -> {
if (DEBUG) Slog.d(TAG, "app binder died");
removeSelf();
}, 0);
} catch (RemoteException e) {
- Slog.w(TAG, "linkToDeath() on mAppCallback failed: " + e);
+ Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
}
}
-
// FillServiceCallbacks
@Override
public void onFillRequestSuccess(FillResponse response) {
@@ -528,7 +642,7 @@
// FillServiceCallbacks
@Override
public void authenticate(IntentSender intent, Intent fillInIntent) {
- startAuthIntent(intent, fillInIntent);
+ startAuthentication(intent, fillInIntent);
}
// FillServiceCallbacks
@@ -550,6 +664,30 @@
.sendToTarget();
}
+ public void setAuthenticationResultLocked(Bundle data) {
+ if (mCurrentResponse == null || data == null) {
+ removeSelf();
+ } else {
+ Parcelable result = data.getParcelable(
+ AutoFillManager.EXTRA_AUTHENTICATION_RESULT);
+ if (result instanceof FillResponse) {
+ mCurrentResponse = (FillResponse) result;
+ processResponseLocked(mCurrentResponse);
+ } else if (result instanceof Dataset) {
+ Dataset dataset = (Dataset) result;
+ final int datasetIndex = Helper.indexOfDataset(
+ dataset.getName(), mCurrentResponse);
+ if (datasetIndex <= 0) {
+ Slog.e(TAG, "Response for a dataset auth has"
+ + " an invalid dataset result: " + dataset.getName());
+ }
+ mCurrentResponse.getDatasets().removeAt(datasetIndex);
+ mCurrentResponse.getDatasets().add(dataset);
+ autoFill(dataset);
+ }
+ }
+ }
+
/**
* Show the save UI, when session can be saved.
*/
@@ -599,29 +737,7 @@
private void callSaveLocked() {
if (DEBUG) Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
- // TODO(b/33197203): hookup extras and make sure they're tested by CTS
- final Bundle extras = null;
-// // TODO(b/33197203): make sure the extras are tested by CTS
-// final Bundle responseExtras = mCurrentResponse == null ? null
-// : mCurrentResponse.getExtras();
-// final Bundle datasetExtras = mAutoFilledDataset == null ? null
-// : mAutoFilledDataset.getExtras();
-// final Bundle extras = (responseExtras == null && datasetExtras == null)
-// ? null : new Bundle();
-// if (responseExtras != null) {
-// if (DEBUG) {
-// Slog.d(TAG, "response extras on save extras: "
-// + bundleToString(responseExtras));
-// }
-// extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
-// }
-// if (datasetExtras != null) {
-// if (DEBUG) {
-// Slog.d(TAG, "dataset extras on save extras: " + bundleToString(datasetExtras));
-// }
-// extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
-// }
-
+ final Bundle extras = this.mCurrentResponse.getExtras();
for (Entry<AutoFillId, ViewState> entry : mViewStates.entrySet()) {
final AutoFillValue value = entry.getValue().mAutoFillValue;
@@ -661,11 +777,11 @@
ViewState viewState = mViewStates.get(id);
if (viewState == null) {
- viewState = new ViewState(id, this);
+ viewState = new ViewState(this, id, this);
mViewStates.put(id, viewState);
}
- if ((flags & FLAG_START_SESSION) != 0 ) {
+ if ((flags & FLAG_START_SESSION) != 0) {
// View is triggering auto-fill.
mCurrentViewState = viewState;
viewState.update(value, bounds);
@@ -738,38 +854,24 @@
private void processResponseLocked(FillResponse response) {
if (DEBUG) Slog.d(TAG, "processResponseLocked(authRequired="
- + response.getAuthentication() +"):" + response);
+ + response.getAuthentication() + "):" + response);
// TODO(b/33197203): add MetricsLogger calls
+ if (mCurrentViewState == null) {
+ // TODO(b/33197203): temporary sanity check; should never happen
+ Slog.w(TAG, "processResponseLocked(): mCurrentResponse is null");
+ return;
+ }
+
mCurrentResponse = response;
if (mCurrentResponse.getAuthentication() != null) {
// Handle authentication.
- final Intent fillInIntent = createAuthFillInIntent(response.getId(), mStructure,
- new Bundle(), new FillCallback(new IFillCallback.Stub() {
- @Override
- public void onCancellable(ICancellationSignal cancellation) {
- // TODO(b/33197203): Handle cancellation
- }
+ final Intent fillInIntent = createAuthFillInIntent(mStructure);
- @Override
- public void onSuccess(FillResponse response) {
- mCurrentResponse = createAuthenticatedResponse(
- mCurrentResponse, response);
- processResponseLocked(mCurrentResponse);
- }
-
- @Override
- public void onFailure(CharSequence message) {
- getUiForShowing().showError(message);
- removeSelf();
- }
- }));
-
- getUiForShowing().showFillResponseAuthRequest(
- mCurrentResponse.getAuthentication(), fillInIntent);
- return;
+ mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
+ return;
}
final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
@@ -782,10 +884,7 @@
return;
}
- // TODO(b/33197203): Consider using mCurrentResponse, depends on partitioning design
- if (mCurrentViewState != null) {
- mCurrentViewState.setResponse(mCurrentResponse);
- }
+ mCurrentViewState.setResponse(mCurrentResponse);
}
void autoFill(Dataset dataset) {
@@ -797,48 +896,24 @@
}
// ...or handle authentication.
- Intent fillInIntent = createAuthFillInIntent(dataset.getId(), mStructure,
- new Bundle(), new FillCallback(new IFillCallback.Stub() {
- @Override
- public void onCancellable(ICancellationSignal cancellation) {
- // TODO(b/33197203): Handle cancellation
- }
-
- @Override
- public void onSuccess(FillResponse response) {
- mCurrentResponse = createAuthenticatedResponse(
- mCurrentResponse, response);
- final Dataset augmentedDataset = Helper.findDatasetById(dataset.getId(),
- mCurrentResponse);
- if (augmentedDataset != null) {
- autoFill(augmentedDataset);
- }
- }
-
- @Override
- public void onFailure(CharSequence message) {
- getUiForShowing().showError(message);
- removeSelf();
- }
- }));
-
- startAuthIntent(dataset.getAuthentication(), fillInIntent);
+ Intent fillInIntent = createAuthFillInIntent(mStructure);
+ startAuthentication(dataset.getAuthentication(), fillInIntent);
}
}
- private Intent createAuthFillInIntent(String itemId, AssistStructure structure,
- Bundle extras, FillCallback fillCallback) {
+ CharSequence getServiceName() {
+ return AutoFillManagerServiceImpl.this.getServiceName();
+ }
+
+ private Intent createAuthFillInIntent(AssistStructure structure) {
Intent fillInIntent = new Intent();
- fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ITEM_ID, itemId);
- fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_ASSIST_STRUCTURE, structure);
- fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_EXTRAS, extras);
- fillInIntent.putExtra(Intent.EXTRA_AUTO_FILL_CALLBACK, fillCallback);
+ fillInIntent.putExtra(AutoFillManager.EXTRA_ASSIST_STRUCTURE, structure);
return fillInIntent;
}
- private void startAuthIntent(IntentSender intent, Intent fillInIntent) {
+ private void startAuthentication(IntentSender intent, Intent fillInIntent) {
try {
- mAppCallback.startIntentSender(intent, fillInIntent);
+ mClient.authenticate(intent, fillInIntent);
} catch (RemoteException e) {
Slog.e(TAG, "Error launching auth intent", e);
}
@@ -874,7 +949,7 @@
try {
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
- mAppCallback.autoFill(dataset);
+ mClient.autoFill(dataset.getFieldIds(), dataset.getFieldValues());
mAutoFilledDataset = dataset;
} catch (RemoteException e) {
Slog.w(TAG, "Error auto-filling activity: " + e);
@@ -882,16 +957,6 @@
}
}
- void enableSessionLocked() {
- if (DEBUG) Slog.d(TAG, "enableSessionLocked()");
-
- try {
- mAppCallback.enableSession();
- } catch (RemoteException e) {
- Slog.w(TAG, "Error enabling session: " + e);
- }
- }
-
private AutoFillUI getUiForShowing() {
synchronized (mLock) {
mUi.setCallbackLocked(this, mActivityToken);
@@ -946,81 +1011,5 @@
mSessions.remove(mActivityToken);
}
}
-
- /**
- * Creates a response from the {@code original} and an {@code update} by
- * replacing all items that needed authentication (response or datasets)
- * with their updated version if the latter does not need authentication.
- * New datasets that don't require auth are appended.
- *
- * @param original The original response requiring auth at some level.
- * @param update An updated response with auth not needed anymore at some level.
- * @return A new response with updated items where auth is not needed anymore.
- */
- // TODO(b/33197203) Unit test
- FillResponse createAuthenticatedResponse(FillResponse original, FillResponse update) {
- // Can update only if ids match
- if (!original.getId().equals(update.getId())) {
- return original;
- }
-
- // If the original required auth and the update doesn't, the update wins
- // but only if none of the update's datasets requires authentication.
- if (original.getAuthentication() != null && update.getAuthentication() == null) {
- ArraySet<Dataset> updateDatasets = update.getDatasets();
- final int udpateDatasetCount = updateDatasets.size();
- for (int i = 0; i < udpateDatasetCount; i++) {
- Dataset updateDataset = updateDatasets.valueAt(i);
- if (updateDataset.getAuthentication() != null) {
- return original;
- }
- }
- return update;
- }
-
- // If no auth on response level we create a response that has all
- // datasets from the original with the ones that required auth but
- // not anymore updated and new ones not requiring auth appended.
-
- // The update shouldn't require auth
- if (update.getAuthentication() != null) {
- return original;
- }
-
- final FillResponse.Builder builder = new FillResponse.Builder(original.getId());
-
- // Update existing datasets
- final ArraySet<Dataset> origDatasets = original.getDatasets();
- final int origDatasetCount = origDatasets.size();
- for (int i = 0; i < origDatasetCount; i++) {
- Dataset origDataset = origDatasets.valueAt(i);
- ArraySet<Dataset> updateDatasets = update.getDatasets();
- final int updateDatasetCount = updateDatasets.size();
- for (int j = 0; j < updateDatasetCount; j++) {
- Dataset updateDataset = updateDatasets.valueAt(j);
- if (origDataset.getId().equals(updateDataset.getId())) {
- // The update shouldn't require auth
- if (updateDataset.getAuthentication() == null) {
- origDataset = updateDataset;
- updateDatasets.removeAt(j);
- }
- break;
- }
- }
- builder.addDataset(origDataset);
- }
-
- // Add new datasets
- final ArraySet<Dataset> updateDatasets = update.getDatasets();
- final int updateDatasetCount = updateDatasets.size();
- for (int i = 0; i < updateDatasetCount; i++) {
- final Dataset updateDataset = updateDatasets.valueAt(i);
- builder.addDataset(updateDataset);
- }
-
- // For now no extras and savable id updates.
-
- return builder.build();
- }
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 0763c74..e83dc1e 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -30,15 +30,13 @@
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
+import android.service.autofill.Dataset;
import android.util.ArraySet;
import android.os.Looper;
import android.text.format.DateUtils;
import android.util.Slog;
-import android.view.autofill.Dataset;
-import android.view.autofill.FillResponse;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Toast;
@@ -58,19 +56,15 @@
private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS;
private static final int MSG_HIDE_SNACK_BAR = 1;
- private static final String EXTRA_AUTH_INTENT_SENDER =
- "com.android.server.autofill.extra.AUTH_INTENT_SENDER";
- private static final String EXTRA_AUTH_FILL_IN_INTENT =
- "com.android.server.autofill.extra.AUTH_FILL_IN_INTENT";
-
private final Context mContext;
private final WindowManager mWm;
// TODO(b/33197203) Fix locking - some state requires lock and some not - requires refactoring
+ private final Object mLock = new Object();
// Fill UI variables
private AnchoredWindow mFillWindow;
- private DatasetPicker mFillView;
+ private View mFillView;
private ViewState mViewState;
private AutoFillUiCallback mCallback;
@@ -158,62 +152,76 @@
UiThread.getHandler().runWithScissors(() -> {
hideSnackbarUiThread();
- hideFillResponseAuthUiUiThread();
}, 0);
- if (datasets == null) {
+ if (datasets == null && viewState.mAuthIntent == null) {
// TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be
// safe, otherwise it would crash system server...
Slog.wtf(TAG, "showFillUI(): no dataset");
return;
}
+ // TODO(b/33197203): should not display UI after we launched an authentication intent, since
+ // we have no warranty the provider will call onFailure() if the authentication failed or
+ // user dismissed the auth window
+ // because if the service does not handle calling the callback,
+
+
UiThread.getHandler().runWithScissors(() -> {
+ // The dataset picker is only shown when authentication is not required...
+ DatasetPicker datasetPicker = null;
+
if (mViewState == null || !mViewState.mId.equals(viewState.mId)) {
hideFillUiUiThread();
mViewState = viewState;
+ if (viewState.mAuthIntent != null) {
+ final CharSequence serviceName = viewState.getServiceName();
- mFillView = new DatasetPicker(mContext, datasets,
- (dataset) -> {
- final AutoFillUiCallback callback;
- synchronized (mLock) {
- callback = mCallback;
- }
- if (callback != null) {
- callback.fill(dataset);
- } else {
- Slog.w(TAG, "null callback on showFillUi() for " + viewState.mId);
- }
- hideFillUi();
- });
+ mFillView = new SignInPrompt(mContext, serviceName, (e) -> {
+ final IntentSender intentSender = viewState.mResponse.getAuthentication();
+ final AutoFillUiCallback callback;
+ final Intent authIntent;
+ synchronized (mLock) {
+ callback = mCallback;
+ authIntent = viewState.mAuthIntent;
+ // Must reset the authentication intent so UI display the datasets after
+ // the user authenticated.
+ viewState.mAuthIntent = null;
+ }
+ if (callback != null) {
+ callback.authenticate(intentSender, authIntent);
+ } else {
+ // TODO(b/33197203): need to figure out why it's null sometimes
+ Slog.w(TAG, "no callback on showFillUi().auth for " + viewState.mId);
+ }
+ });
+ } else {
+ mFillView = datasetPicker = new DatasetPicker(mContext, datasets,
+ (dataset) -> {
+ final AutoFillUiCallback callback;
+ synchronized (mLock) {
+ callback = mCallback;
+ }
+ if (callback != null) {
+ callback.fill(dataset);
+ } else {
+ // TODO(b/33197203): need to figure out why it's null sometimes
+ Slog.w(TAG, "no callback on showFillUi() for " + viewState.mId);
+ }
+ hideFillUiUiThread();
+ });
+ }
mFillWindow = new AnchoredWindow(mWm, appToken, mFillView);
- if (DEBUG) Slog.d(TAG, "showFillUi(): view changed");
+ if (DEBUG) Slog.d(TAG, "showFillUi(): view changed for: " + viewState.mId);
}
-
- if (DEBUG) Slog.d(TAG, "showFillUi(): bounds=" + bounds + ", filterText=" + filterText);
- mFillView.update(filterText);
+ if (datasetPicker != null) {
+ datasetPicker.update(filterText);
+ }
mFillWindow.show(bounds);
- }, 0);
- }
- /**
- * Shows an UI affordance indicating that user action is required before a {@link FillResponse}
- * can be used.
- *
- * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
- * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
- */
- void showFillResponseAuthRequest(IntentSender intent, Intent fillInIntent) {
- if (!hasCallback()) {
- return;
- }
- hideAll();
- UiThread.getHandler().runWithScissors(() -> {
- // TODO(b/33197203): proper implementation
- showFillResponseAuthUiUiThread(intent, fillInIntent);
}, 0);
}
@@ -251,14 +259,12 @@
UiThread.getHandler().runWithScissors(() -> {
hideSnackbarUiThread();
hideFillUiUiThread();
- hideFillResponseAuthUiUiThread();
}, 0);
}
void dump(PrintWriter pw) {
pw.println("AufoFill UI");
final String prefix = " ";
- pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
@@ -311,103 +317,4 @@
void fill(Dataset dataset);
void save();
}
-
- /////////////////////////////////////////////////////////////////////////////////
- // TODO(b/33197203): temporary code using a notification to request auto-fill. //
- // Will be removed once UX decide the right way to present it to the user. //
- /////////////////////////////////////////////////////////////////////////////////
-
- // TODO(b/33197203): remove from frameworks/base/core/res/AndroidManifest.xml once not used
- private static final String NOTIFICATION_AUTO_FILL_INTENT =
- "com.android.internal.autofill.action.REQUEST_AUTOFILL";
-
- private BroadcastReceiver mNotificationReceiver;
- private final Object mLock = new Object();
-
- // Hack used to generate unique pending intents
- static int sResultCode = 0;
-
- private void ensureNotificationListener() {
- synchronized (mLock) {
- if (mNotificationReceiver == null) {
- mNotificationReceiver = new NotificationReceiver();
- mContext.registerReceiver(mNotificationReceiver,
- new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
- }
- }
- }
-
- final class NotificationReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final AutoFillUiCallback callback;
- synchronized (mLock) {
- callback = mCallback;
- }
- if (callback != null) {
- IntentSender intentSender = intent.getParcelableExtra(EXTRA_AUTH_INTENT_SENDER);
- Intent fillInIntent = intent.getParcelableExtra(EXTRA_AUTH_FILL_IN_INTENT);
- callback.authenticate(intentSender, fillInIntent);
- }
- collapseStatusBar();
- }
- }
-
- @android.annotation.UiThread
- private void showFillResponseAuthUiUiThread(IntentSender intent, Intent fillInIntent) {
- final String title = "AutoFill Authentication";
- final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");
-
- final Intent authIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
- authIntent.putExtra(EXTRA_AUTH_INTENT_SENDER, intent);
- authIntent.putExtra(EXTRA_AUTH_FILL_IN_INTENT, fillInIntent);
-
- final PendingIntent authPendingIntent = PendingIntent.getBroadcast(
- mContext, ++sResultCode, authIntent, PendingIntent.FLAG_ONE_SHOT);
-
- subTitle.append("Tap notification to launch its authentication UI.");
-
- final Notification.Builder notification = newNotificationBuilder()
- .setAutoCancel(true)
- .setOngoing(false)
- .setContentTitle(title)
- .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString()))
- .setContentIntent(authPendingIntent);
-
- ensureNotificationListener();
-
- final long identity = Binder.clearCallingIdentity();
- try {
- NotificationManager.from(mContext).notify(0, notification.build());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @android.annotation.UiThread
- private void hideFillResponseAuthUiUiThread() {
- final long identity = Binder.clearCallingIdentity();
- try {
- NotificationManager.from(mContext).cancel(0);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private Notification.Builder newNotificationBuilder() {
- return new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- }
-
- private void collapseStatusBar() {
- final StatusBarManager sbm = (StatusBarManager) mContext.getSystemService("statusbar");
- sbm.collapsePanels();
- }
- /////////////////////////////////////////
- // End of temporary notification code. //
- /////////////////////////////////////////
}
diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
index 9bee61a..a54cab9 100644
--- a/services/autofill/java/com/android/server/autofill/DatasetPicker.java
+++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
@@ -17,9 +17,9 @@
import android.content.Context;
import android.graphics.Color;
+import android.service.autofill.Dataset;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.view.autofill.Dataset;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 0f2bb60..48ae635 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -18,11 +18,11 @@
import android.annotation.Nullable;
import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
import android.util.ArraySet;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
-import android.view.autofill.Dataset;
-import android.view.autofill.FillResponse;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,25 +78,25 @@
}
/**
- * Finds a data set by id in a response.
+ * Finds the index of a data set given its name.
*
- * @param id The dataset id.
+ * @param name The dataset name.
* @param response The response to search.
- * @return The dataset if found or null.
+ * @return The index of dataset if found or -1.
*/
- static Dataset findDatasetById(String id, FillResponse response) {
+ static int indexOfDataset(CharSequence name, FillResponse response) {
ArraySet<Dataset> datasets = response.getDatasets();
if (datasets == null || datasets.isEmpty()) {
- return null;
+ return -1;
}
final int datasetCount = datasets.size();
for (int i = 0; i < datasetCount; i++) {
Dataset dataset = datasets.valueAt(i);
- if (dataset.getId().equals(id)) {
- return dataset;
+ if (dataset.getName().toString().equals(name.toString())) {
+ return i;
}
}
- return null;
+ return -1;
}
private Helper() {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index c070f77..6502e7f 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -31,12 +31,12 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.autofill.AutoFillService;
+import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.text.format.DateUtils;
import android.util.Slog;
-import android.view.autofill.FillResponse;
import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
@@ -191,7 +191,7 @@
ensureBound();
} else {
if (DEBUG) {
- Slog.d(LOG_TAG, "[user: " + mUserId + "] handleOnFillRequest()");
+ Slog.d(LOG_TAG, "[user: " + mUserId + "] handlePendingRequest()");
}
pendingRequest.run();
}
diff --git a/services/autofill/java/com/android/server/autofill/SignInPrompt.java b/services/autofill/java/com/android/server/autofill/SignInPrompt.java
new file mode 100644
index 0000000..6d17acd
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SignInPrompt.java
@@ -0,0 +1,38 @@
+/*
+ * 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.autofill;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * A view displaying the sign-in prompt for an auto-fill service.
+ */
+final class SignInPrompt extends Button {
+
+ SignInPrompt(Context context, CharSequence serviceName, View.OnClickListener listener) {
+ super(context);
+ // TODO(b/33197203): use strings.xml
+ final String text = serviceName != null
+ ? "Sign in to " + serviceName + " to autofill"
+ : "Sign in to autofill";
+
+ // TODO(b/33197203): polish UI / use better altenative than a button...
+ setText(text);
+ setOnClickListener(listener);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6bf0e8d..8fbc520 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -599,15 +599,42 @@
static final int OP_ACKNOWLEDGED = 1;
static final int OP_TIMEOUT = -1;
- class Operation {
- public int state;
- public BackupRestoreTask callback;
+ private static final int OP_TYPE_WAIT = 0; // Waiting for BackupAgent.
+ private static final int OP_TYPE_BACKUP = 1; // Backup operation in progress.
- Operation(int initialState, BackupRestoreTask callbackObj) {
+ class Operation {
+ int state;
+ final BackupRestoreTask callback;
+ final int type;
+
+ Operation(int initialState, BackupRestoreTask callbackObj, int type) {
state = initialState;
callback = callbackObj;
+ this.type = type;
}
}
+
+ /**
+ * mCurrentOperations contains the list of currently active operations.
+ *
+ * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+ * An operation wraps a BackupRestoreTask within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * A BackupRestore task gets notified of ack/timeout for the operation via
+ * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+ * on the mCurrentOpLock. {@link BackupManagerService#waitUntilOperationComplete(int)} is
+ * used in various places to 'wait' for notifyAll and detect change of pending state of an
+ * operation. So typically, an operation will be removed from this array by:
+ * - BackupRestoreTask#handleCancel and
+ * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+ * these places because waitUntilOperationComplete relies on the operation being present to
+ * determine its completion status.
+ *
+ * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+ * cancel backup tasks.
+ */
+ @GuardedBy("mCurrentOpLock")
final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
@@ -967,7 +994,8 @@
case MSG_TIMEOUT:
{
- handleTimeout(msg.arg1, msg.obj);
+ Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
+ handleCancel(msg.arg1, false);
break;
}
@@ -2364,6 +2392,38 @@
return BackupManager.SUCCESS;
}
+ // Cancel all running backups.
+ public void cancelBackups(){
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "cancelBackups() called.");
+ }
+ final long oldToken = Binder.clearCallingIdentity();
+ try {
+ List<Integer> operationsToCancel = new ArrayList<>();
+ synchronized (mCurrentOpLock) {
+ for (int i = 0; i < mCurrentOperations.size(); i++) {
+ Operation op = mCurrentOperations.valueAt(i);
+ int token = mCurrentOperations.keyAt(i);
+ if (op.type == OP_TYPE_BACKUP) {
+ operationsToCancel.add(token);
+ }
+ }
+
+ for (Integer token : operationsToCancel) {
+ handleCancel(token, true /* cancelAll */);
+ }
+ }
+
+ // We don't want the backup jobs to kick in any time soon.
+ // Reschedules them to run in the distant future.
+ KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS);
+ FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS);
+ } finally {
+ Binder.restoreCallingIdentity(oldToken);
+ }
+ }
+
// -----
// Interface and methods used by the asynchronous-with-timeout backup/restore operations
@@ -2375,20 +2435,34 @@
void operationComplete(long result);
// An operation that wanted a callback has timed out
- void handleTimeout();
+ void handleCancel(boolean cancelAll);
}
- void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+ void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+ int operationType) {
if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ " interval=" + interval + " callback=" + callback);
synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+ mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
mBackupHandler.sendMessageDelayed(msg, interval);
}
}
+ private void removeOperation(int token) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
+ }
+ synchronized (mCurrentOpLock) {
+ if (mCurrentOperations.get(token) == null) {
+ Slog.w(TAG, "Duplicate remove for operation. token=" +
+ Integer.toHexString(token));
+ }
+ mCurrentOperations.remove(token);
+ }
+ }
+
// synchronous waiter case
boolean waitUntilOperationComplete(int token) {
if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
@@ -2405,9 +2479,14 @@
if (op.state == OP_PENDING) {
try {
mCurrentOpLock.wait();
- } catch (InterruptedException e) {}
+ } catch (InterruptedException e) {
+ }
// When the wait is notified we loop around and recheck the current state
} else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Unblocked waiting for operation token=" +
+ Integer.toHexString(token));
+ }
// No longer pending; we're done
finalState = op.state;
break;
@@ -2416,33 +2495,36 @@
}
}
+ removeOperation(token);
mBackupHandler.removeMessages(MSG_TIMEOUT);
if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
+ " complete: finalState=" + finalState);
return finalState == OP_ACKNOWLEDGED;
}
- void handleTimeout(int token, Object obj) {
+ void handleCancel(int token, boolean cancelAll) {
// Notify any synchronous waiters
Operation op = null;
synchronized (mCurrentOpLock) {
op = mCurrentOperations.get(token);
if (MORE_DEBUG) {
- if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+ if (op == null) Slog.w(TAG, "Cancel of token " + Integer.toHexString(token)
+ " but no op found");
}
int state = (op != null) ? op.state : OP_TIMEOUT;
if (state == OP_ACKNOWLEDGED) {
// The operation finished cleanly, so we have nothing more to do.
- if (MORE_DEBUG) {
- Slog.v(TAG, "handleTimeout() after success; cleanup happens now");
+ if (DEBUG) {
+ Slog.w(TAG, "Operation already got an ack." +
+ "Should have been removed from mCurrentOperations.");
}
op = null;
mCurrentOperations.delete(token);
} else if (state == OP_PENDING) {
- if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+ if (DEBUG) Slog.v(TAG, "Cancel: token=" + Integer.toHexString(token));
op.state = OP_TIMEOUT;
- // Leaves the object in place for later ack
+ // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
+ // called after we receive cancel here. We need this op's state there.
}
mCurrentOpLock.notifyAll();
}
@@ -2450,9 +2532,9 @@
// If there's a TimeoutHandler for this event, call it
if (op != null && op.callback != null) {
if (MORE_DEBUG) {
- Slog.v(TAG, " Invoking timeout on " + op.callback);
+ Slog.v(TAG, " Invoking cancel on " + op.callback);
}
- op.callback.handleTimeout();
+ op.callback.handleCancel(cancelAll);
}
}
@@ -2464,9 +2546,36 @@
FINAL
}
+ /**
+ * This class handles the process of backing up a given list of key/value backup packages.
+ * Also takes in a list of pending dolly backups and kicks them off when key/value backups
+ * are done.
+ *
+ * Flow:
+ * If required, backup @pm@.
+ * For each pending key/value backup package:
+ * - Bind to agent.
+ * - Call agent.doBackup()
+ * - Wait either for cancel/timeout or operationComplete() callback from the agent.
+ * Start task to perform dolly backups.
+ *
+ * There are three entry points into this class:
+ * - execute() [Called from the handler thread]
+ * - operationComplete(long result) [Called from the handler thread]
+ * - handleCancel(boolean cancelAll) [Can be called from any thread]
+ * These methods synchronize on mCancelLock.
+ *
+ * Interaction with mCurrentOperations:
+ * - An entry for this task is put into mCurrentOperations for the entire lifetime of the
+ * task. This is useful to cancel the task if required.
+ * - An ephemeral entry is put into mCurrentOperations each time we are waiting on for
+ * response from a backup agent. This is used to plumb timeouts and completion callbacks.
+ */
class PerformBackupTask implements BackupRestoreTask {
private static final String TAG = "PerformBackupTask";
+ private final Object mCancelLock = new Object();
+
IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
@@ -2477,6 +2586,10 @@
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final PerformFullTransportBackupTask mFullBackupTask;
+ private final int mCurrentOpToken;
+ private volatile int mEphemeralOpToken;
+
// carried information about the current in-flight operation
IBackupAgent mAgentBinder;
PackageInfo mCurrentPackage;
@@ -2491,6 +2604,8 @@
final boolean mUserInitiated;
final boolean mNonIncremental;
+ private volatile boolean mCancelAll;
+
public PerformBackupTask(IBackupTransport transport, String dirName,
ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
IBackupManagerMonitor monitor, ArrayList<String> pendingFullBackups,
@@ -2505,33 +2620,63 @@
mNonIncremental = nonIncremental;
mStateDir = new File(mBaseStateDir, dirName);
+ mCurrentOpToken = generateToken();
mCurrentState = BackupState.INITIAL;
mFinished = false;
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] fullBackups =
+ mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+ mFullBackupTask =
+ new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null,
+ fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch,
+ mObserver, mMonitor,mUserInitiated);
+
+ registerTask();
addBackupTrace("STATE => INITIAL");
}
+ /**
+ * Put this task in the repository of running tasks.
+ */
+ private void registerTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_BACKUP));
+ }
+ }
+
+ /**
+ * Remove this task from repository of running tasks.
+ */
+ private void unregisterTask() {
+ removeOperation(mCurrentOpToken);
+ }
+
// Main entry point: perform one chunk of work, updating the state as appropriate
// and reposting the next chunk to the primary backup handler thread.
@Override
+ @GuardedBy("mCancelLock")
public void execute() {
- switch (mCurrentState) {
- case INITIAL:
- beginBackup();
- break;
+ synchronized (mCancelLock) {
+ switch (mCurrentState) {
+ case INITIAL:
+ beginBackup();
+ break;
- case RUNNING_QUEUE:
- invokeNextAgent();
- break;
+ case RUNNING_QUEUE:
+ invokeNextAgent();
+ break;
- case FINAL:
- if (!mFinished) finalizeBackup();
- else {
- Slog.e(TAG, "Duplicate finish");
- }
- mFinished = true;
- break;
+ case FINAL:
+ if (!mFinished) finalizeBackup();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
}
}
@@ -2796,6 +2941,12 @@
void finalizeBackup() {
addBackupTrace("finishing");
+ // Mark packages that we didn't backup (because backup was cancelled, etc.) as needing
+ // backup.
+ for (BackupRequest req : mQueue) {
+ dataChangedImpl(req.packageName);
+ }
+
// Either backup was successful, in which case we of course do not need
// this pass's journal any more; or it failed, in which case we just
// re-enqueued all of these packages in the current active journal.
@@ -2850,7 +3001,9 @@
clearBackupTrace();
- if (mStatus == BackupTransport.TRANSPORT_OK &&
+ unregisterTask();
+
+ if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
CountDownLatch latch = new CountDownLatch(1);
@@ -2862,8 +3015,12 @@
mObserver, mMonitor, mUserInitiated);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
- (new Thread(task, "full-transport-requested")).start();
+ (new Thread(mFullBackupTask, "full-transport-requested")).start();
+ } else if (mCancelAll) {
+ mFullBackupTask.unregisterTask();
+ sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
sendBackupFinished(mObserver, BackupManager.SUCCESS);
@@ -2905,8 +3062,8 @@
mBackupData = null;
mNewState = null;
- final int token = generateToken();
boolean callingAgent = false;
+ mEphemeralOpToken = generateToken();
try {
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
@@ -2945,10 +3102,11 @@
// Initiate the target's backup pass
addBackupTrace("setting timeout");
- prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this,
+ OP_TYPE_WAIT);
addBackupTrace("calling agent doBackup()");
- agent.doBackup(mSavedState, mBackupData, mNewState, quota, token,
+ agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
@@ -3060,195 +3218,225 @@
}
@Override
+ @GuardedBy("mCancelLock")
public void operationComplete(long unusedResult) {
- // The agent reported back to us!
-
- if (mBackupData == null) {
- // This callback was racing with our timeout, so we've cleaned up the
- // agent state already and are on to the next thing. We have nothing
- // further to do here: agent state having been cleared means that we've
- // initiated the appropriate next operation.
- final String pkg = (mCurrentPackage != null)
- ? mCurrentPackage.packageName : "[none]";
- if (MORE_DEBUG) {
- Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ // The agent reported back to us!
+ if (mFinished) {
+ Slog.d(TAG, "operationComplete received after task finished.");
+ return;
}
- addBackupTrace("late opComplete; curPkg = " + pkg);
- return;
- }
- final String pkgName = mCurrentPackage.packageName;
- final long filepos = mBackupDataName.length();
- FileDescriptor fd = mBackupData.getFileDescriptor();
- try {
- // If it's a 3rd party app, see whether they wrote any protected keys
- // and complain mightily if they are attempting shenanigans.
- if (mCurrentPackage.applicationInfo != null &&
- (mCurrentPackage.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
- ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
- ParcelFileDescriptor.MODE_READ_ONLY);
- BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
- try {
- while (in.readNextHeader()) {
- final String key = in.getKey();
- if (key != null && key.charAt(0) >= 0xff00) {
- // Not okay: crash them and bail.
- failAgent(mAgentBinder, "Illegal backup key: " + key);
- addBackupTrace("illegal key " + key + " from " + pkgName);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
- "bad key");
- mBackupHandler.removeMessages(MSG_TIMEOUT);
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_AGENT_FAILURE);
- errorCleanup();
- // errorCleanup() implicitly executes next state properly
- return;
- }
- in.skipEntityData();
- }
- } finally {
- if (readFd != null) {
- readFd.close();
- }
+ if (mBackupData == null) {
+ // This callback was racing with our timeout, so we've cleaned up the
+ // agent state already and are on to the next thing. We have nothing
+ // further to do here: agent state having been cleared means that we've
+ // initiated the appropriate next operation.
+ final String pkg = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName : "[none]";
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
}
+ addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
}
- // Piggyback the widget state payload, if any
- writeWidgetPayloadIfAppropriate(fd, pkgName);
- } catch (IOException e) {
- // Hard disk error; recovery/failure policy TBD. For now roll back,
- // but we may want to consider this a transport-level failure (i.e.
- // we're in such a bad state that we can't contemplate doing backup
- // operations any more during this pass).
- Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ final String pkgName = mCurrentPackage.packageName;
+ final long filepos = mBackupDataName.length();
+ FileDescriptor fd = mBackupData.getFileDescriptor();
try {
- Os.ftruncate(fd, filepos);
- } catch (ErrnoException ee) {
- Slog.w(TAG, "Unable to roll back!");
- }
- }
-
- // Spin the data off to the transport and proceed with the next stage.
- if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
- + pkgName);
- mBackupHandler.removeMessages(MSG_TIMEOUT);
- clearAgentState();
- addBackupTrace("operation complete");
-
- ParcelFileDescriptor backupData = null;
- mStatus = BackupTransport.TRANSPORT_OK;
- long size = 0;
- try {
- size = mBackupDataName.length();
- if (size > 0) {
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- backupData = ParcelFileDescriptor.open(mBackupDataName,
+ // If it's a 3rd party app, see whether they wrote any protected keys
+ // and complain mightily if they are attempting shenanigans.
+ if (mCurrentPackage.applicationInfo != null &&
+ (mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ == 0) {
+ ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
- addBackupTrace("sending data to transport");
- int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
+ try {
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ if (key != null && key.charAt(0) >= 0xff00) {
+ // Not okay: crash them and bail.
+ failAgent(mAgentBinder, "Illegal backup key: " + key);
+ addBackupTrace("illegal key " + key + " from " + pkgName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
+ "bad key");
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ errorCleanup();
+ // agentErrorCleanup() implicitly executes next state properly
+ return;
+ }
+ in.skipEntityData();
+ }
+ } finally {
+ if (readFd != null) {
+ readFd.close();
+ }
+ }
}
- // TODO - We call finishBackup() for each application backed up, because
- // we need to know now whether it succeeded or failed. Instead, we should
- // hold off on finishBackup() until the end, which implies holding off on
- // renaming *all* the output state files (see below) until that happens.
-
- addBackupTrace("data delivered: " + mStatus);
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
- addBackupTrace("finished: " + mStatus);
- } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- addBackupTrace("transport rejected package");
+ // Piggyback the widget state payload, if any
+ writeWidgetPayloadIfAppropriate(fd, pkgName);
+ } catch (IOException e) {
+ // Hard disk error; recovery/failure policy TBD. For now roll back,
+ // but we may want to consider this a transport-level failure (i.e.
+ // we're in such a bad state that we can't contemplate doing backup
+ // operations any more during this pass).
+ Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ try {
+ Os.ftruncate(fd, filepos);
+ } catch (ErrnoException ee) {
+ Slog.w(TAG, "Unable to roll back!");
}
- } else {
- if (MORE_DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
- addBackupTrace("no data to send");
}
- if (mStatus == BackupTransport.TRANSPORT_OK) {
- // After successful transport, delete the now-stale data
- // and juggle the files so that next time we supply the agent
- // with the new state file it just created.
- mBackupDataName.delete();
- mNewStateName.renameTo(mSavedStateName);
- sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
- logBackupComplete(pkgName);
- } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- // The transport has rejected backup of this specific package. Roll it
- // back but proceed with running the rest of the queue.
- mBackupDataName.delete();
- mNewStateName.delete();
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
- EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
- } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
- EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
- } else {
- // Actual transport-level failure to communicate the data to the backend
+ // Spin the data off to the transport and proceed with the next stage.
+ if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+ + pkgName);
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ clearAgentState();
+ addBackupTrace("operation complete");
+
+ ParcelFileDescriptor backupData = null;
+ mStatus = BackupTransport.TRANSPORT_OK;
+ long size = 0;
+ try {
+ size = mBackupDataName.length();
+ if (size > 0) {
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ backupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ addBackupTrace("sending data to transport");
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ }
+
+ // TODO - We call finishBackup() for each application backed up, because
+ // we need to know now whether it succeeded or failed. Instead, we should
+ // hold off on finishBackup() until the end, which implies holding off on
+ // renaming *all* the output state files (see below) until that happens.
+
+ addBackupTrace("data delivered: " + mStatus);
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ addBackupTrace("finishing op on transport");
+ mStatus = mTransport.finishBackup();
+ addBackupTrace("finished: " + mStatus);
+ } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ addBackupTrace("transport rejected package");
+ }
+ } else {
+ if (MORE_DEBUG) Slog.i(TAG,
+ "no backup data written; not calling transport");
+ addBackupTrace("no data to send");
+ }
+
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ // After successful transport, delete the now-stale data
+ // and juggle the files so that next time we supply the agent
+ // with the new state file it just created.
+ mBackupDataName.delete();
+ mNewStateName.renameTo(mSavedStateName);
+ sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+ logBackupComplete(pkgName);
+ } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ // The transport has rejected backup of this specific package. Roll it
+ // back but proceed with running the rest of the queue.
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
+ EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
+ EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+ } else {
+ // Actual transport-level failure to communicate the data to the backend
+ sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
+ }
+ } catch (Exception e) {
sendBackupOnPackageResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_ABORTED);
+ Slog.e(TAG, "Transport error backing up " + pkgName, e);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
- }
- } catch (Exception e) {
- sendBackupOnPackageResult(mObserver, pkgName,
- BackupManager.ERROR_TRANSPORT_ABORTED);
- Slog.e(TAG, "Transport error backing up " + pkgName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
- mStatus = BackupTransport.TRANSPORT_ERROR;
- } finally {
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- }
-
- final BackupState nextState;
- if (mStatus == BackupTransport.TRANSPORT_OK
- || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
- // Success or single-package rejection. Proceed with the next app if any,
- // otherwise we're done.
- nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
- } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
- if (MORE_DEBUG) {
- Slog.d(TAG, "Package " + mCurrentPackage.packageName +
- " hit quota limit on k/v backup");
- }
- if (mAgentBinder != null) {
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ } finally {
try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, false);
- mAgentBinder.doQuotaExceeded(size, quota);
- } catch (Exception e) {
- Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+ if (backupData != null) backupData.close();
+ } catch (IOException e) {
}
}
- nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
- } else {
- // Any other error here indicates a transport-level failure. That means
- // we need to halt everything and reschedule everything for next time.
- revertAndEndBackup();
- nextState = BackupState.FINAL;
- }
- executeNextState(nextState);
+ final BackupState nextState;
+ if (mStatus == BackupTransport.TRANSPORT_OK
+ || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ // Success or single-package rejection. Proceed with the next app if any,
+ // otherwise we're done.
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package " + mCurrentPackage.packageName +
+ " hit quota limit on k/v backup");
+ }
+ if (mAgentBinder != null) {
+ try {
+ long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
+ false);
+ mAgentBinder.doQuotaExceeded(size, quota);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+ }
+ }
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else {
+ // Any other error here indicates a transport-level failure. That means
+ // we need to halt everything and reschedule everything for next time.
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ }
+
+ executeNextState(nextState);
+ }
}
+
@Override
- public void handleTimeout() {
- // Whoops, the current agent timed out running doBackup(). Tidy up and restage
- // it for the next time we run a backup pass.
- // !!! TODO: keep track of failure counts per agent, and blacklist those which
- // fail repeatedly (i.e. have proved themselves to be buggy).
- Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
- "timeout");
- mMonitor = monitorEvent(mMonitor,
- BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
- addBackupTrace("timeout of " + mCurrentPackage.packageName);
- errorCleanup();
- dataChangedImpl(mCurrentPackage.packageName);
+ @GuardedBy("mCancelLock")
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ if (mFinished) {
+ // We have already cancelled this operation.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stale cancel. cancelAll=" + cancelAll);
+ }
+ return;
+ }
+ mCancelAll = cancelAll;
+ // Whoops, the current agent timed out running doBackup(). Tidy up and restage
+ // it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
+ Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+ addBackupTrace(
+ "cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
+ errorCleanup();
+ if (!cancelAll) {
+ executeNextState(
+ mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+ dataChangedImpl(mCurrentPackage.packageName);
+ } else {
+ finalizeBackup();
+ }
+ }
}
void revertAndEndBackup() {
@@ -3276,8 +3464,6 @@
mBackupDataName.delete();
mNewStateName.delete();
clearAgentState();
-
- executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
}
// Cleanup common to both success and failure cases
@@ -3289,7 +3475,7 @@
// Current-operation callback handling requires the validity of these various
// bits of internal state as an invariant of the operation still being live.
// This means we make sure to clear all of the state in unison inside the lock.
- mCurrentOperations.clear();
+ mCurrentOperations.remove(mEphemeralOpToken);
mSavedState = mBackupData = mNewState = null;
}
@@ -3343,7 +3529,7 @@
try {
pipes = ParcelFileDescriptor.createPipe();
int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null, OP_TYPE_WAIT);
mService.backupObbs(pkg.packageName, pipes[1], token, mBackupManagerBinder);
routeSocketDataToOutput(pipes[0], out);
success = waitUntilOperationComplete(token);
@@ -3481,6 +3667,7 @@
boolean mIncludeApks;
PackageInfo mPkg;
private final long mQuota;
+ private final int mOpToken;
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
@@ -3535,7 +3722,7 @@
if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL,
- mTimeoutMonitor /* in parent class */);
+ mTimeoutMonitor /* in parent class */, OP_TYPE_WAIT);
mAgent.doFullBackup(mPipe, mQuota, mToken, mBackupManagerBinder);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -3551,7 +3738,7 @@
}
FullBackupEngine(OutputStream output, FullBackupPreflight preflightHook, PackageInfo pkg,
- boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota) {
+ boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
mOutput = output;
mPreflightHook = preflightHook;
mPkg = pkg;
@@ -3561,6 +3748,7 @@
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
mQuota = quota;
+ mOpToken = opToken;
}
public int preflightCheck() throws RemoteException {
@@ -3603,9 +3791,8 @@
byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName,
UserHandle.USER_SYSTEM);
- final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1],
- token, sendApk, !isSharedStorage, widgetBlob);
+ mOpToken, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "app-data-runner");
@@ -3614,7 +3801,7 @@
// Now pull data from the app and stuff it into the output
routeSocketDataToOutput(pipes[0], mOutput);
- if (!waitUntilOperationComplete(token)) {
+ if (!waitUntilOperationComplete(mOpToken)) {
Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
} else {
if (MORE_DEBUG) {
@@ -3865,12 +4052,14 @@
PackageInfo mCurrentTarget;
String mCurrentPassword;
String mEncryptPassword;
+ private final int mCurrentOpToken;
PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
boolean includeApks, boolean includeObbs, boolean includeShared,
boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
super(observer);
+ mCurrentOpToken = generateToken();
mLatch = latch;
mOutputFile = fd;
@@ -4156,8 +4345,7 @@
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks,
- this /* BackupRestoreTask */, Long.MAX_VALUE /* quota */);
+ mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
// Don't need to check preflight result as there is no preflight hook.
@@ -4190,9 +4378,6 @@
} catch (IOException e) {
/* nothing we can do about this */
}
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
synchronized (mLatch) {
mLatch.set(true);
mLatch.notifyAll();
@@ -4216,29 +4401,68 @@
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
final PackageInfo target = mCurrentTarget;
if (DEBUG) {
- Slog.w(TAG, "adb backup timeout of " + target);
+ Slog.w(TAG, "adb backup cancel of " + target);
}
if (target != null) {
tearDownAgentAndKill(mCurrentTarget.applicationInfo);
}
+ removeOperation(mCurrentOpToken);
}
}
- // Full backup task extension used for transport-oriented operation
- class PerformFullTransportBackupTask extends FullBackupTask {
+ /**
+ * Full backup task extension used for transport-oriented operation.
+ *
+ * Flow:
+ * For each requested package:
+ * - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package.
+ * - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking())
+ * - If preflight data size is within limit, start reading data from agent pipe and writing
+ * to transport pipe. While there is data to send, call transport.sendBackupData(int) to
+ * tell the transport how many bytes to expect on its pipe.
+ * - After sending all data, call transport.finishBackup() if things went well. And
+ * transport.cancelFullBackup() otherwise.
+ *
+ * Interactions with mCurrentOperations:
+ * - An entry for this object is added to mCurrentOperations for the entire lifetime of this
+ * object. Used to cancel the operation.
+ * - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries
+ * to get timeouts or operation complete callbacks.
+ *
+ * Handling cancels:
+ * - The contract we provide is that the task won't interact with the transport after
+ * handleCancel() is done executing.
+ * - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe
+ * and 3. Get backup result from mBackupRunner.
+ * - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the
+ * preflight operation which counts down on the preflight latch. 2. Tears down the agent,
+ * so read() returns -1. 3. Notifies mCurrentOpLock which unblocks
+ * mBackupRunner.getBackupResultBlocking().
+ */
+ class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
static final String TAG = "PFTBT";
+
+ private final Object mCancelLock = new Object();
+
ArrayList<PackageInfo> mPackages;
PackageInfo mCurrentPackage;
boolean mUpdateSchedule;
CountDownLatch mLatch;
- AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
IBackupObserver mBackupObserver;
IBackupManagerMonitor mMonitor;
boolean mUserInitiated;
+ private volatile IBackupTransport mTransport;
+ SinglePackageBackupRunner mBackupRunner;
+ private final int mBackupRunnerOpToken;
+
+ // This is true when a backup operation for some package is in progress.
+ private volatile boolean mIsDoingBackup;
+ private volatile boolean mCancelAll;
+ private final int mCurrentOpToken;
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
@@ -4247,12 +4471,15 @@
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
- mKeepRunning = new AtomicBoolean(true);
mJob = runningJob;
mPackages = new ArrayList<PackageInfo>(whichPackages.length);
mBackupObserver = backupObserver;
mMonitor = monitor;
mUserInitiated = userInitiated;
+ mCurrentOpToken = generateToken();
+ mBackupRunnerOpToken = generateToken();
+
+ registerTask();
for (String pkg : whichPackages) {
try {
@@ -4298,12 +4525,60 @@
}
}
- public void setRunning(boolean running) {
- mKeepRunning.set(running);
+ private void registerTask() {
+ synchronized (mCurrentOpLock) {
+ Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_BACKUP));
+ }
+ }
+
+ private void unregisterTask() {
+ removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public void execute() {
+ // Nothing to do.
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ synchronized (mCancelLock) {
+ // We only support 'cancelAll = true' case for this task. Cancelling of a single package
+
+ // due to timeout is handled by SinglePackageBackupRunner and SinglePackageBackupPreflight.
+
+ if (!cancelAll) {
+ Slog.wtf(TAG, "Expected cancelAll to be true.");
+ }
+
+ if (mCancelAll) {
+ Slog.d(TAG, "Ignoring duplicate cancel call.");
+ return;
+ }
+
+ mCancelAll = true;
+ if (mIsDoingBackup) {
+ BackupManagerService.this.handleCancel(mBackupRunnerOpToken, cancelAll);
+ try {
+ mTransport.cancelFullBackup();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
+ // Can't do much.
+ }
+ }
+ }
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ // Nothing to do.
}
@Override
public void run() {
+
// data from the app, passed to us for bridging to the transport
ParcelFileDescriptor[] enginePipes = null;
@@ -4325,8 +4600,8 @@
return;
}
- IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ mTransport = mTransportManager.getCurrentTransportBinder();
+ if (mTransport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
return;
@@ -4347,30 +4622,40 @@
// Tell the transport the data's coming
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- int backupPackageStatus = transport.performFullBackup(currentPackage,
- transportPipes[0], flags);
+ int backupPackageStatus;
+ long quota = Long.MAX_VALUE;
+ synchronized (mCancelLock) {
+ if (mCancelAll) {
+ break;
+ }
+ backupPackageStatus = mTransport.performFullBackup(currentPackage,
+ transportPipes[0], flags);
+
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ quota = mTransport.getBackupQuota(currentPackage.packageName,
+ true /* isFullBackup */);
+ // Now set up the backup engine / data source end of things
+ enginePipes = ParcelFileDescriptor.createPipe();
+ mBackupRunner =
+ new SinglePackageBackupRunner(enginePipes[1], currentPackage,
+ mTransport, quota, mBackupRunnerOpToken);
+ // The runner dup'd the pipe half, so we close it here
+ enginePipes[1].close();
+ enginePipes[1] = null;
+
+ mIsDoingBackup = true;
+ }
+ }
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- final long quota = transport.getBackupQuota(currentPackage.packageName,
- true /* isFullBackup */);
// The transport has its own copy of the read end of the pipe,
// so close ours now
transportPipes[0].close();
transportPipes[0] = null;
- // Now set up the backup engine / data source end of things
- enginePipes = ParcelFileDescriptor.createPipe();
-
- SinglePackageBackupRunner backupRunner =
- new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- transport, quota);
- // The runner dup'd the pipe half, so we close it here
- enginePipes[1].close();
- enginePipes[1] = null;
-
// Spin off the runner to fetch the app's data and pipe it
// into the engine pipes
- (new Thread(backupRunner, "package-backup-bridge")).start();
+ (new Thread(mBackupRunner, "package-backup-bridge")).start();
// Read data off the engine pipe and pass it to the transport
// pipe until we hit EOD on the input stream. We do not take
@@ -4380,7 +4665,7 @@
FileOutputStream out = new FileOutputStream(
transportPipes[1].getFileDescriptor());
long totalRead = 0;
- final long preflightResult = backupRunner.getPreflightResultBlocking();
+ final long preflightResult = mBackupRunner.getPreflightResultBlocking();
// Preflight result is negative if some error happened on preflight.
if (preflightResult < 0) {
if (MORE_DEBUG) {
@@ -4392,19 +4677,17 @@
} else {
int nRead = 0;
do {
- if (!mKeepRunning.get()) {
- if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Full backup task told to stop");
- }
- break;
- }
nRead = in.read(buffer);
if (MORE_DEBUG) {
Slog.v(TAG, "in.read(buffer) from app: " + nRead);
}
if (nRead > 0) {
out.write(buffer, 0, nRead);
- backupPackageStatus = transport.sendBackupData(nRead);
+ synchronized (mCancelLock) {
+ if (!mCancelAll) {
+ backupPackageStatus = mTransport.sendBackupData(nRead);
+ }
+ }
totalRead += nRead;
if (mBackupObserver != null && preflightResult > 0) {
sendBackupOnUpdate(mBackupObserver, packageName,
@@ -4413,29 +4696,32 @@
}
} while (nRead > 0
&& backupPackageStatus == BackupTransport.TRANSPORT_OK);
-
// Despite preflight succeeded, package still can hit quota on flight.
if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
Slog.w(TAG, "Package hit quota limit in-flight " + packageName
+ ": " + totalRead + " of " + quota);
- backupRunner.sendQuotaExceeded(totalRead, quota);
+ mBackupRunner.sendQuotaExceeded(totalRead, quota);
}
}
- // If we've lost our running criteria, tell the transport to cancel
- // and roll back this (partial) backup payload; otherwise tell it
- // that we've reached the clean finish state.
- if (!mKeepRunning.get()) {
- backupPackageStatus = BackupTransport.TRANSPORT_ERROR;
- transport.cancelFullBackup();
- } else {
- // If we were otherwise in a good state, now interpret the final
- // result based on what finishBackup() returns. If we're in a
- // failure case already, preserve that result and ignore whatever
- // finishBackup() reports.
- final int finishResult = transport.finishBackup();
- if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- backupPackageStatus = finishResult;
+ final int backupRunnerResult = mBackupRunner.getBackupResultBlocking();
+
+ synchronized (mCancelLock) {
+ mIsDoingBackup = false;
+ // If mCancelCurrent is true, we have already called cancelFullBackup().
+ if (!mCancelAll) {
+ if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
+ // If we were otherwise in a good state, now interpret the final
+ // result based on what finishBackup() returns. If we're in a
+ // failure case already, preserve that result and ignore whatever
+ // finishBackup() reports.
+ final int finishResult = mTransport.finishBackup();
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ backupPackageStatus = finishResult;
+ }
+ } else {
+ mTransport.cancelFullBackup();
+ }
}
}
@@ -4449,8 +4735,7 @@
// errors take precedence over agent/app-specific errors for purposes of
// determining our course of action.
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- // We still could fail in backup runner thread, getting result from there.
- int backupRunnerResult = backupRunner.getBackupResultBlocking();
+ // We still could fail in backup runner thread.
if (backupRunnerResult != BackupTransport.TRANSPORT_OK) {
// If there was an error in runner thread and
// not TRANSPORT_ERROR here, overwrite it.
@@ -4474,7 +4759,7 @@
// Also ask the transport how long it wants us to wait before
// moving on to the next package, if any.
- backoff = transport.requestFullBackupTime();
+ backoff = mTransport.requestFullBackupTime();
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Transport suggested backoff=" + backoff);
}
@@ -4513,6 +4798,14 @@
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
+ sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_BACKUP_CANCELLED);
+ Slog.w(TAG, "Backup cancelled. package=" + packageName +
+ ", cancelAll=" + mCancelAll);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
+ tearDownAgentAndKill(currentPackage.applicationInfo);
+ // Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -4542,6 +4835,11 @@
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
Slog.w(TAG, "Exception trying full transport backup", e);
} finally {
+
+ if (mCancelAll) {
+ backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
+ }
+
if (DEBUG) {
Slog.i(TAG, "Full backup completed with status: " + backupRunStatus);
}
@@ -4550,6 +4848,8 @@
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
+ unregisterTask();
+
if (mJob != null) {
mJob.finishBackupPass();
}
@@ -4565,6 +4865,7 @@
if (mUpdateSchedule) {
scheduleNextFullBackupJob(backoff);
}
+
Slog.i(BackupManagerService.TAG, "Full data backup pass finished.");
mWakelock.release();
}
@@ -4601,23 +4902,24 @@
final CountDownLatch mLatch = new CountDownLatch(1);
final IBackupTransport mTransport;
final long mQuota;
+ private final int mCurrentOpToken;
- public SinglePackageBackupPreflight(IBackupTransport transport, long quota) {
+ SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
mTransport = transport;
mQuota = quota;
+ mCurrentOpToken = currentOpToken;
}
@Override
public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
int result;
try {
- final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this);
+ prepareOperationTimeout(mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_WAIT);
addBackupTrace("preflighting");
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
- agent.doMeasureFullBackup(mQuota, token, mBackupManagerBinder);
+ agent.doMeasureFullBackup(mQuota, mCurrentOpToken, mBackupManagerBinder);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
@@ -4652,7 +4954,7 @@
@Override
public void execute() {
- // Unused in this case
+ // Unused.
}
@Override
@@ -4663,15 +4965,17 @@
}
mResult.set(result);
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Preflight timeout; failing");
+ Slog.i(TAG, "Preflight cancelled; failing");
}
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
@@ -4688,43 +4992,67 @@
class SinglePackageBackupRunner implements Runnable, BackupRestoreTask {
final ParcelFileDescriptor mOutput;
final PackageInfo mTarget;
- final FullBackupPreflight mPreflight;
+ final SinglePackageBackupPreflight mPreflight;
final CountDownLatch mPreflightLatch;
final CountDownLatch mBackupLatch;
+ private final int mCurrentOpToken;
+ private final int mEphemeralToken;
private FullBackupEngine mEngine;
private volatile int mPreflightResult;
private volatile int mBackupResult;
private final long mQuota;
+ private volatile boolean mIsCancelled;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- IBackupTransport transport, long quota) throws IOException {
+ IBackupTransport transport, long quota, int currentOpToken) throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
- mPreflight = new SinglePackageBackupPreflight(transport, quota);
+ mCurrentOpToken = currentOpToken;
+ mEphemeralToken = generateToken();
+ mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
mBackupResult = BackupTransport.AGENT_ERROR;
mQuota = quota;
+ registerTask();
+ }
+
+ void registerTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+ OP_TYPE_WAIT));
+ }
+ }
+
+ void unregisterTask() {
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.remove(mCurrentOpToken);
+ }
}
@Override
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
- mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota);
+ mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota, mCurrentOpToken);
try {
try {
- mPreflightResult = mEngine.preflightCheck();
+ if (!mIsCancelled) {
+ mPreflightResult = mEngine.preflightCheck();
+ }
} finally {
mPreflightLatch.countDown();
}
// If there is no error on preflight, continue backup.
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
- mBackupResult = mEngine.backupOnePackage();
+ if (!mIsCancelled) {
+ mBackupResult = mEngine.backupOnePackage();
+ }
}
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
} finally {
+ unregisterTask();
mBackupLatch.countDown();
try {
mOutput.close();
@@ -4743,6 +5071,9 @@
long getPreflightResultBlocking() {
try {
mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
return mPreflight.getExpectedSizeOrErrorCode();
} else {
@@ -4756,6 +5087,9 @@
int getBackupResultBlocking() {
try {
mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
return mBackupResult;
} catch (InterruptedException e) {
return BackupTransport.AGENT_ERROR;
@@ -4772,14 +5106,23 @@
public void operationComplete(long result) { /* intentionally empty */ }
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (DEBUG) {
- Slog.w(TAG, "Full backup timeout of " + mTarget.packageName);
+ Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
}
+
mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mIsCancelled = true;
+ // Cancel tasks spun off by this task.
+ BackupManagerService.this.handleCancel(mEphemeralToken, cancelAll);
tearDownAgentAndKill(mTarget.applicationInfo);
+ // Free up everyone waiting on this task and its children.
+ mPreflightLatch.countDown();
+ mBackupLatch.countDown();
+ // We are done with this operation.
+ removeOperation(mCurrentOpToken);
}
}
}
@@ -5057,7 +5400,7 @@
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Telling running backup to stop");
}
- mRunningFullBackupTask.setRunning(false);
+ mRunningFullBackupTask.handleCancel(true);
}
}
}
@@ -5191,6 +5534,8 @@
// Widget blob to be restored out-of-band
byte[] mWidgetData = null;
+ private final int mEphemeralOpToken;
+
// Runner that can be placed in a separate thread to do in-process
// invocations of the full restore API asynchronously. Used by adb restore.
class RestoreFileRunnable implements Runnable {
@@ -5226,7 +5571,9 @@
}
public FullRestoreEngine(BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
- PackageInfo onlyPackage, boolean allowApks, boolean allowObbs) {
+ PackageInfo onlyPackage, boolean allowApks, boolean allowObbs,
+ int ephemeralOpToken) {
+ mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
mOnlyPackage = onlyPackage;
@@ -5431,17 +5778,16 @@
if (okay) {
boolean agentSuccess = true;
long toCopy = info.size;
- final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL,
- mMonitorTask);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_FULL_BACKUP_INTERVAL,
+ mMonitorTask, OP_TYPE_WAIT);
if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
- info.mtime, token, mBackupManagerBinder);
+ info.mtime, mEphemeralOpToken, mBackupManagerBinder);
} else {
if (MORE_DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
@@ -5452,12 +5798,12 @@
if (mTargetApp.processName.equals("system")) {
Slog.d(TAG, "system process agent - spinning a thread");
RestoreFileRunnable runner = new RestoreFileRunnable(
- mAgent, info, mPipes[0], token);
+ mAgent, info, mPipes[0], mEphemeralOpToken);
new Thread(runner, "restore-sys-runner").start();
} else {
mAgent.doRestoreFile(mPipes[0], info.size, info.type,
info.domain, info.path, info.mode, info.mtime,
- token, mBackupManagerBinder);
+ mEphemeralOpToken, mBackupManagerBinder);
}
}
} catch (IOException e) {
@@ -5509,7 +5855,7 @@
// and now that we've sent it all, wait for the remote
// side to acknowledge receipt
- agentSuccess = waitUntilOperationComplete(token);
+ agentSuccess = waitUntilOperationComplete(mEphemeralOpToken);
}
// okay, if the remote end failed at any point, deal with
@@ -6354,9 +6700,11 @@
class AdbRestoreFinishedLatch implements BackupRestoreTask {
static final String TAG = "AdbRestoreFinishedLatch";
final CountDownLatch mLatch;
+ private final int mCurrentOpToken;
- AdbRestoreFinishedLatch() {
+ AdbRestoreFinishedLatch(int currentOpToken) {
mLatch = new CountDownLatch(1);
+ mCurrentOpToken = currentOpToken;
}
void await() {
@@ -6379,14 +6727,16 @@
Slog.w(TAG, "adb onRestoreFinished() complete");
}
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
if (DEBUG) {
Slog.w(TAG, "adb onRestoreFinished() timed out");
}
mLatch.countDown();
+ removeOperation(mCurrentOpToken);
}
}
@@ -6577,9 +6927,6 @@
Slog.w(TAG, "Close of restore data pipe threw", e);
/* nothing we can do about this */
}
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
synchronized (mLatchObject) {
mLatchObject.set(true);
mLatchObject.notifyAll();
@@ -6868,7 +7215,8 @@
long toCopy = info.size;
final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+ OP_TYPE_WAIT);
if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
@@ -7003,8 +7351,9 @@
// In the adb restore case, we do restore-finished here
if (doRestoreFinished) {
final int token = generateToken();
- final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch();
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch);
+ final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(token);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch,
+ OP_TYPE_WAIT);
if (mTargetApp.processName.equals("system")) {
if (MORE_DEBUG) {
Slog.d(TAG, "system agent - restoreFinished on thread");
@@ -7872,11 +8221,14 @@
ParcelFileDescriptor mBackupData;
ParcelFileDescriptor mNewState;
+ private final int mEphemeralOpToken;
+
// Invariant: mWakelock is already held, and this task is responsible for
// releasing it at the end of the restore operation.
PerformUnifiedRestoreTask(IBackupTransport transport, IRestoreObserver observer,
IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
int pmToken, boolean isFullSystemRestore, String[] filterSet) {
+ mEphemeralOpToken = generateToken();
mState = UnifiedRestoreState.INITIAL;
mStartRealtime = SystemClock.elapsedRealtime();
@@ -8304,7 +8656,6 @@
ParcelFileDescriptor stage;
File downloadFile = (staging) ? mStageName : mBackupDataName;
- final int token = generateToken();
try {
// Run the transport's restore pass
stage = ParcelFileDescriptor.open(downloadFile,
@@ -8377,9 +8728,9 @@
// Kick off the restore, checking for hung agents. The timeout or
// the operationComplete() callback will schedule the next step,
// so we do not do that here.
- prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_INTERVAL, this, OP_TYPE_WAIT);
mAgent.doRestore(mBackupData, appVersionCode, mNewState,
- token, mBackupManagerBinder);
+ mEphemeralOpToken, mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
@@ -8426,9 +8777,9 @@
// state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
private void restoreFinished() {
try {
- final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_RESTORE_FINISHED_INTERVAL, this);
- mAgent.doRestoreFinished(token, mBackupManagerBinder);
+ prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_FINISHED_INTERVAL, this,
+ OP_TYPE_WAIT);
+ mAgent.doRestoreFinished(mEphemeralOpToken, mBackupManagerBinder);
// If we get this far, the callback or timeout will schedule the
// next restore state, so we're done
} catch (Exception e) {
@@ -8452,7 +8803,10 @@
// pipe through which the engine will read data. [0] read, [1] write
ParcelFileDescriptor[] mEnginePipes;
+ private final int mEphemeralOpToken;
+
public StreamFeederThread() throws IOException {
+ mEphemeralOpToken = generateToken();
mTransportPipes = ParcelFileDescriptor.createPipe();
mEnginePipes = ParcelFileDescriptor.createPipe();
setRunning(true);
@@ -8466,7 +8820,7 @@
EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
mCurrentPackage.packageName);
- mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false);
+ mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false, mEphemeralOpToken);
mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -8605,7 +8959,8 @@
// The app has timed out handling a restoring file
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
if (DEBUG) {
Slog.w(TAG, "Full-data restore target timed out; shutting down");
}
@@ -8785,13 +9140,11 @@
// The caller is responsible for reestablishing the state machine; our
// responsibility here is to clear the decks for whatever comes next.
mBackupHandler.removeMessages(MSG_TIMEOUT, this);
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
}
@Override
public void operationComplete(long unusedResult) {
+ removeOperation(mEphemeralOpToken);
if (MORE_DEBUG) {
Slog.i(TAG, "operationComplete() during restore: target="
+ mCurrentPackage.packageName
@@ -8852,7 +9205,8 @@
// A call to agent.doRestore() or agent.doRestoreFinished() has timed out
@Override
- public void handleTimeout() {
+ public void handleCancel(boolean cancelAll) {
+ removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
@@ -9966,7 +10320,16 @@
// and ignore it; we've already dealt with the timeout.
op = null;
mCurrentOperations.delete(token);
- } else {
+ } else if (op.state == OP_ACKNOWLEDGED) {
+ if (DEBUG) {
+ Slog.w(TAG, "Received duplicate ack for token=" +
+ Integer.toHexString(token));
+ }
+ op = null;
+ mCurrentOperations.remove(token);
+ } else if (op.state == OP_PENDING) {
+ // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
+ // called after we we receive this call.
op.state = OP_ACKNOWLEDGED;
}
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index da0cee5..8855661 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -363,6 +363,14 @@
}
@Override
+ public void cancelBackups() throws RemoteException {
+ BackupManagerService svc = mService;
+ if (svc != null) {
+ svc.cancelBackups();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 719a64e..fe0b840 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4166,7 +4166,7 @@
}
ensureRequestableCapabilities(networkCapabilities);
- if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
+ if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 4559254..2e61550 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -132,6 +132,7 @@
2843 full_backup_success (Package|3)
2844 full_restore_package (Package|3)
2845 full_backup_quota_exceeded (Package|3)
+2846 full_backup_cancelled (Package|3),(Message|3)
2850 backup_transport_lifecycle (Transport|3),(Bound|1|1)
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 5935600..8442c11 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -149,11 +149,15 @@
import java.lang.annotation.Retention;
import java.nio.charset.StandardCharsets;
import java.security.InvalidParameterException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* This class provides a system service that manages input methods.
@@ -525,18 +529,188 @@
* </p>
*/
private static class StartInputInfo {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ final int mSequenceNumber;
+ final long mTimestamp;
+ final long mWallTime;
@NonNull
final IBinder mImeToken;
+ @NonNull
+ final String mImeId;
+ // @InputMethodClient.StartInputReason
+ final int mStartInputReason;
+ final boolean mRestarting;
@Nullable
final IBinder mTargetWindow;
+ @NonNull
+ final EditorInfo mEditorInfo;
+ final int mTargetWindowSoftInputMode;
+ final int mClientBindSequenceNumber;
- StartInputInfo(@NonNull IBinder imeToken, @Nullable IBinder targetWindow) {
+ StartInputInfo(@NonNull IBinder imeToken, @NonNull String imeId,
+ /* @InputMethodClient.StartInputReason */ int startInputReason, boolean restarting,
+ @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo,
+ int targetWindowSoftInputMode, int clientBindSequenceNumber) {
+ mSequenceNumber = sSequenceNumber.getAndIncrement();
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
mImeToken = imeToken;
+ mImeId = imeId;
+ mStartInputReason = startInputReason;
+ mRestarting = restarting;
mTargetWindow = targetWindow;
+ mEditorInfo = editorInfo;
+ mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+ mClientBindSequenceNumber = clientBindSequenceNumber;
}
}
- private WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
+ @GuardedBy("mMethodMap")
+ private final WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
+
+ /**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+ private static final class StartInputHistory {
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16;
+
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private final static int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+ private static int getEntrySize() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+ } else {
+ return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+ }
+ }
+
+ /**
+ * Backing store for the ring bugger.
+ */
+ private final Entry[] mEntries = new Entry[getEntrySize()];
+
+ /**
+ * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
+ * write.
+ */
+ private int mNextIndex = 0;
+
+ /**
+ * Recyclable entry to store the information in {@link StartInputInfo}.
+ */
+ private static final class Entry {
+ int mSequenceNumber;
+ long mTimestamp;
+ long mWallTime;
+ @NonNull
+ String mImeTokenString;
+ @NonNull
+ String mImeId;
+ /* @InputMethodClient.StartInputReason */
+ int mStartInputReason;
+ boolean mRestarting;
+ @NonNull
+ String mTargetWindowString;
+ @NonNull
+ EditorInfo mEditorInfo;
+ int mTargetWindowSoftInputMode;
+ int mClientBindSequenceNumber;
+
+ Entry(@NonNull StartInputInfo original) {
+ set(original);
+ }
+
+ void set(@NonNull StartInputInfo original) {
+ mSequenceNumber = original.mSequenceNumber;
+ mTimestamp = original.mTimestamp;
+ mWallTime = original.mWallTime;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mImeTokenString = String.valueOf(original.mImeToken);
+ mImeId = original.mImeId;
+ mStartInputReason = original.mStartInputReason;
+ mRestarting = original.mRestarting;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mTargetWindowString = String.valueOf(original.mTargetWindow);
+ mEditorInfo = original.mEditorInfo;
+ mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+ mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+ }
+ }
+
+ /**
+ * Add a new entry and discard the oldest entry as needed.
+ * @param info {@lin StartInputInfo} to be added.
+ */
+ void addEntry(@NonNull StartInputInfo info) {
+ final int index = mNextIndex;
+ if (mEntries[index] == null) {
+ mEntries[index] = new Entry(info);
+ } else {
+ mEntries[index].set(info);
+ }
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final SimpleDateFormat dataFormat =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")"
+ + " reason="
+ + InputMethodClient.getStartInputReason(entry.mStartInputReason)
+ + " restarting=" + entry.mRestarting);
+
+ pw.print(prefix);
+ pw.println(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+
+ pw.print(prefix);
+ pw.println(" targetWin=" + entry.mTargetWindowString
+ + " [" + entry.mEditorInfo.packageName + "]"
+ + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+ pw.print(prefix);
+ pw.println(" softInputMode=" + InputMethodClient.softInputModeToString(
+ entry.mTargetWindowSoftInputMode));
+
+ pw.print(prefix);
+ pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+ + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+ + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+ + " fieldName=" + entry.mEditorInfo.fieldName
+ + " actionId=" + entry.mEditorInfo.actionId
+ + " actionLabel=" + entry.mEditorInfo.actionLabel);
+ }
+ }
+ }
+
+ @GuardedBy("mMethodMap")
+ @NonNull
+ private final StartInputHistory mStartInputHistory = new StartInputHistory();
class SettingsObserver extends ContentObserver {
int mUserId;
@@ -727,7 +901,8 @@
mPackagesToMonitorComponentChange.add(packageName);
}
- private boolean isChangingPackagesOfCurrentUser() {
+ @GuardedBy("mMethodMap")
+ private boolean isChangingPackagesOfCurrentUserLocked() {
final int userId = getChangingUserId();
final boolean retval = userId == mSettings.getCurrentUserId();
if (DEBUG) {
@@ -740,10 +915,10 @@
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
- if (!isChangingPackagesOfCurrentUser()) {
- return false;
- }
synchronized (mMethodMap) {
+ if (!isChangingPackagesOfCurrentUserLocked()) {
+ return false;
+ }
String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
if (curInputMethodId != null) {
@@ -777,10 +952,10 @@
@Override
public void onSomePackagesChanged() {
- if (!isChangingPackagesOfCurrentUser()) {
- return;
- }
synchronized (mMethodMap) {
+ if (!isChangingPackagesOfCurrentUserLocked()) {
+ return;
+ }
InputMethodInfo curIm = null;
String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
@@ -1379,8 +1554,11 @@
}
final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mCurToken, mCurFocusedWindow);
+ final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
+ !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
+ mCurSeq);
mStartInputMap.put(startInputToken, info);
+ mStartInputHistory.addEntry(info);
final SessionState session = mCurClient.curSession;
executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
@@ -1855,23 +2033,20 @@
@Override
public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
int backDisposition) {
- if (startInputToken == null) {
- throw new InvalidParameterException("startInputToken cannot be null");
- }
-
if (!calledWithValidToken(token)) {
return;
}
+ final StartInputInfo info;
synchronized (mMethodMap) {
- final StartInputInfo info = mStartInputMap.get(startInputToken);
- if (info == null) {
- throw new InvalidParameterException("Unknown startInputToken=" + startInputToken);
- }
+ info = mStartInputMap.get(startInputToken);
mImeWindowVis = vis;
mBackDisposition = backDisposition;
updateSystemUiLocked(token, vis, backDisposition);
}
+ mWindowManagerInternal.updateInputMethodWindowStatus(token,
+ (vis & InputMethodService.IME_VISIBLE) != 0,
+ token != null ? info.mTargetWindow : null);
}
private void updateSystemUi(IBinder token, int vis, int backDisposition) {
@@ -4152,6 +4327,9 @@
mSwitchingController.dump(p);
p.println(" mSettings:");
mSettings.dumpLocked(p, " ");
+
+ p.println(" mStartInputHistory:");
+ mStartInputHistory.dump(pw, " ");
}
p.println(" ");
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 42eb958..ef7780c 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.annotation.NonNull;
import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
@@ -135,8 +136,6 @@
private static final String FUSED_LOCATION_SERVICE_ACTION =
"com.android.location.service.FusedLocationProvider";
- private static final String GMSCORE_PACKAGE = "com.android.google.gms";
-
private static final int MSG_LOCATION_CHANGED = 1;
private static final long NANOS_PER_MILLI = 1000000L;
@@ -224,7 +223,7 @@
private final ArrayList<LocationProviderProxy> mProxyProviders =
new ArrayList<>();
- private String[] mBackgroundThrottlePackageWhitelist = new String[]{};
+ private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
// current active user on the device - other users are denied location data
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -345,6 +344,8 @@
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
updateUserProfiles(mCurrentUserId);
+ updateThrottlingWhitelistLocked();
+
// prepare providers
loadProvidersLocked();
updateProvidersLocked();
@@ -380,14 +381,7 @@
@Override
public void onChange(boolean selfChange) {
synchronized (mLock) {
- String setting = Settings.Global.getString(
- mContext.getContentResolver(),
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
- if (setting == null) {
- setting = "";
- }
-
- mBackgroundThrottlePackageWhitelist = setting.split(",");
+ updateThrottlingWhitelistLocked();
updateProvidersLocked();
}
}
@@ -1747,12 +1741,27 @@
p.setRequest(providerRequest, worksource);
}
+ private void updateThrottlingWhitelistLocked() {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mBackgroundThrottlePackageWhitelist.clear();
+ mBackgroundThrottlePackageWhitelist.addAll(
+ SystemConfig.getInstance().getAllowUnthrottledLocation());
+ mBackgroundThrottlePackageWhitelist.addAll(
+ Arrays.asList(setting.split(",")));
+ }
+
private boolean isThrottlingExemptLocked(Receiver receiver) {
if (receiver.mUid == Process.SYSTEM_UID) {
return true;
}
- if (receiver.mPackageName.equals(GMSCORE_PACKAGE)) {
+ if (mBackgroundThrottlePackageWhitelist.contains(receiver.mPackageName)) {
return true;
}
@@ -1762,12 +1771,6 @@
}
}
- for (String whitelistedPackage : mBackgroundThrottlePackageWhitelist) {
- if (receiver.mPackageName.equals(whitelistedPackage)) {
- return true;
- }
- }
-
return false;
}
@@ -2999,6 +3002,13 @@
}
}
+ if (!mBackgroundThrottlePackageWhitelist.isEmpty()) {
+ pw.println(" Throttling Whitelisted Packages:");
+ for (String packageName : mBackgroundThrottlePackageWhitelist) {
+ pw.println(" " + packageName);
+ }
+ }
+
pw.append(" fudger: ");
mLocationFudger.dump(fd, pw, args);
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index a073d8e..4a44530 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -20,6 +20,8 @@
import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -61,6 +63,7 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
+import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyProperties;
@@ -79,6 +82,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.SyntheticPasswordManager.AuthenticationToken;
import libcore.util.HexEncoding;
@@ -86,6 +91,7 @@
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -145,12 +151,14 @@
private boolean mFirstCallToVold;
protected IGateKeeperService mGateKeeperService;
+ private SyntheticPasswordManager mSpManager;
+
/**
* The UIDs that are used for system credential storage in keystore.
*/
private static final int[] SYSTEM_CREDENTIAL_UIDS = {
Process.WIFI_UID, Process.VPN_UID,
- Process.ROOT_UID, Process.SYSTEM_UID };
+ Process.ROOT_UID };
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
@@ -335,6 +343,14 @@
}
return null;
}
+
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return new SyntheticPasswordManager(storage);
+ }
+
+ public int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
}
public LockSettingsService(Context context) {
@@ -365,6 +381,8 @@
mUserManager = injector.getUserManager();
mStrongAuthTracker = injector.getStrongAuthTracker();
mStrongAuthTracker.register(mStrongAuth);
+
+ mSpManager = injector.getSyntheticPasswordManager(mStorage);
}
/**
@@ -801,17 +819,42 @@
@Override
public boolean havePassword(int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) ==
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ }
// Do we need a permissions check here?
return mStorage.hasPassword(userId);
}
@Override
public boolean havePattern(int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) ==
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ }
+ }
// Do we need a permissions check here?
return mStorage.hasPattern(userId);
}
private boolean isUserSecure(int userId) {
+ synchronized (mSpManager) {
+ try {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) !=
+ LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+ } catch (RemoteException e) {
+ // fall through
+ }
+ }
return mStorage.hasCredential(userId);
}
@@ -1021,6 +1064,13 @@
private void setLockCredentialInternal(String credential, int credentialType,
String savedCredential, int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
+ userId);
+ return;
+ }
+ }
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
if (credential != null) {
Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
@@ -1061,7 +1111,16 @@
savedCredential = null;
}
}
-
+ synchronized (mSpManager) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
+ currentHandle.type, userId);
+ spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
+ userId);
+ return;
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
userId);
if (enrolledHandle != null) {
@@ -1189,6 +1248,11 @@
return hash;
}
+ private void setAuthlessUserKeyProtection(int userId, byte[] key) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setAuthlessUserKeyProtectiond: user=" + userId);
+ addUserKeyAuth(userId, null, key);
+ }
+
private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr)
throws RemoteException {
if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
@@ -1320,7 +1384,16 @@
if (TextUtils.isEmpty(credential)) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
-
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ VerifyCredentialResponse response = spBasedDoVerifyCredentialLocked(credential,
+ credentialType, hasChallenge, challenge, userId, progressCallback);
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ }
+ return response;
+ }
+ }
CredentialHash storedHash = mStorage.readCredentialHash(userId);
if (storedHash.type != credentialType) {
Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
@@ -1456,8 +1529,8 @@
notifyActivePasswordMetricsAvailable(credential, userId);
unlockKeystore(credential, userId);
- Slog.i(TAG, "Unlocking user " + userId +
- " with token length " + response.getPayload().length);
+ Slog.i(TAG, "Unlocking user " + userId + " with token length "
+ + response.getPayload().length);
unlockUser(userId, response.getPayload(), secretFromCredential(credential));
if (isManagedProfileWithSeparatedLock(userId)) {
@@ -1467,6 +1540,15 @@
}
if (shouldReEnroll) {
setLockCredentialInternal(credential, storedHash.type, credential, userId);
+ } else {
+ // Now that we've cleared of all required GK migration, let's do the final
+ // migration to synthetic password.
+ synchronized (mSpManager) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ initializeSyntheticPasswordLocked(storedHash.hash, credential,
+ storedHash.type, userId);
+ }
+ }
}
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
if (response.getTimeout() > 0) {
@@ -1697,7 +1779,7 @@
}
}
- private synchronized IGateKeeperService getGateKeeperService()
+ protected synchronized IGateKeeperService getGateKeeperService()
throws RemoteException {
if (mGateKeeperService != null) {
return mGateKeeperService;
@@ -1713,4 +1795,431 @@
Slog.e(TAG, "Unable to acquire GateKeeperService");
return null;
}
+
+ /**
+ * Precondition: vold and keystore unlocked.
+ *
+ * Create new synthetic password, set up synthetic password blob protected by the supplied
+ * user credential, and make the newly-created SP blob active.
+ *
+ * The invariant under a synthetic password is:
+ * 1. If user credential exists, then both vold and keystore and protected with keys derived
+ * from the synthetic password.
+ * 2. If user credential does not exist, vold and keystore protection are cleared. This is to
+ * make it consistent with current behaviour. It also allows ActivityManager to call
+ * unlockUser() with empty secret.
+ * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
+ * whether the user changes his lockscreen PIN or clear/reset it. When the user clears its
+ * lockscreen PIN, we still maintain the existing synthetic password in a password blob
+ * protected by a default PIN. The only exception is when the DPC performs an untrusted
+ * credential change, in which case we have no way to derive the existing synthetic password
+ * and has to create a new one.
+ * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
+ * clears/re-creates his lockscreen PIN.
+ *
+ *
+ * Different cases of calling this method:
+ * 1. credentialHash != null
+ * This implies credential != null, a new SP blob will be provisioned, and existing SID
+ * migrated to associate with the new SP.
+ * This happens during a normal migration case when the user currently has password.
+ *
+ * 2. credentialhash == null and credential == null
+ * A new SP blob and a new SID will be created, while the user has no credentials.
+ * This can happens when we are activating an escrow token on a unsecured device, during
+ * which we want to create the SP structure with an empty user credential.
+ *
+ * 3. credentialhash == null and credential != null
+ * This is the untrusted credential reset, OR the user sets a new lockscreen password
+ * FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
+ */
+ private AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
+ String credential, int credentialType, int userId) throws RemoteException {
+ Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
+ AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
+ credentialHash, credential, userId);
+ if (auth == null) {
+ Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
+ return null;
+ }
+ long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
+ credential, credentialType, auth, userId);
+ if (credential != null) {
+ if (credentialHash == null) {
+ // Since when initializing SP, we didn't provide an existing password handle
+ // for it to migrate SID, we need to create a new SID for the user.
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+ }
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ } else {
+ clearUserKeyProtection(userId);
+ setKeystorePassword(null, userId);
+ getGateKeeperService().clearSecureUserId(userId);
+ }
+ fixateNewestUserKeyAuth(userId);
+ setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId);
+ return auth;
+ }
+
+ private long getSyntheticPasswordHandleLocked(int userId) {
+ try {
+ return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId);
+ } catch (RemoteException e) {
+ return SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+ }
+
+ private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ // This is a global setting
+ long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
+ return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+
+ private boolean shouldMigrateToSyntheticPasswordLocked(int userId) throws RemoteException {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ // This is a global setting
+ long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
+ return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+
+ private void enableSyntheticPasswordLocked() throws RemoteException {
+ setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+ }
+
+ private VerifyCredentialResponse spBasedDoVerifyCredentialLocked(String userCredential, int
+ credentialType, boolean hasChallenge, long challenge, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "spBasedDoVerifyCredentialLocked: user=" + userId);
+ if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ userCredential = null;
+ }
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, userCredential, userId);
+
+ VerifyCredentialResponse response = authResult.gkResponse;
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ // credential has matched
+ // perform verifyChallenge with synthetic password which generates the real auth
+ // token for the current user
+ response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
+ challenge, userId);
+ if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ Slog.wtf(TAG, "verifyChallenge with SP failed.");
+ return VerifyCredentialResponse.ERROR;
+ }
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
+ notifyActivePasswordMetricsAvailable(userCredential, userId);
+ unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
+
+ final byte[] secret = authResult.authToken.deriveDiskEncryptionKey();
+ Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length);
+ unlockUser(userId, null, secret);
+
+ if (isManagedProfileWithSeparatedLock(userId)) {
+ TrustManager trustManager =
+ (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+ trustManager.setDeviceLockedForUser(userId, false);
+ }
+ activateEscrowTokens(authResult.authToken, userId);
+ } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ if (response.getTimeout() > 0) {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Change the user's lockscreen password by creating a new SP blob and update the handle, based
+ * on an existing authentication token. Even though a new SP blob is created, the underlying
+ * synthetic password is never changed.
+ *
+ * When clearing credential, we keep the SP unchanged, but clear its password handle so its
+ * SID is gone. We also clear password from (software-based) keystore and vold, which will be
+ * added back when new password is set in future.
+ */
+ private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
+ AuthenticationToken auth, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+ long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
+ credential, credentialType, auth, userId);
+ final Map<Integer, String> profilePasswords;
+ if (credential != null) {
+ // // not needed by synchronizeUnifiedWorkChallengeForProfiles()
+ profilePasswords = null;
+
+ if (mSpManager.hasSidForUser(userId)) {
+ // We are changing password of a secured device, nothing more needed as
+ // createPasswordBasedSyntheticPassword has already taken care of maintaining
+ // the password handle and SID unchanged.
+
+ //refresh auth token
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ } else {
+ // A new password is set on a previously-unsecured device, we need to generate
+ // a new SID, and re-add keys to vold and keystore.
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ fixateNewestUserKeyAuth(userId);
+ setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ }
+ } else {
+ // Cache all profile password if they use unified work challenge. This will later be
+ // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
+ profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
+
+ // we are clearing password of a secured device, so need to nuke SID as well.
+ mSpManager.clearSidForUser(userId);
+ getGateKeeperService().clearSecureUserId(userId);
+ // Clear key from vold so ActivityManager can just unlock the user with empty secret
+ // during boot.
+ clearUserKeyProtection(userId);
+ fixateNewestUserKeyAuth(userId);
+ setKeystorePassword(null, userId);
+ }
+ setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ return newHandle;
+ }
+
+ private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
+ String savedCredential, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when managed profile has unified lock
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "Child profile key not found");
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
+ }
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationToken auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, savedCredential, userId).authToken;
+ if (auth != null) {
+ // We are performing a trusted credential change i.e. a correct existing credential
+ // is provided
+ setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+ } else {
+ // We are performing an untrusted credential change i.e. by DevicePolicyManager.
+ // So provision a new SP and SID. This would invalidate existing escrow tokens.
+ // Still support this for now but this flow will be removed in the next release.
+
+ Slog.w(TAG, "Untrusted credential change invoked");
+ initializeSyntheticPasswordLocked(null, credential, credentialType, userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+ mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+ }
+ notifyActivePasswordMetricsAvailable(credential, userId);
+
+ }
+
+ @Override
+ public long addEscrowToken(byte[] token, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
+ synchronized (mSpManager) {
+ enableSyntheticPasswordLocked();
+ // Migrate to synthetic password based credentials if ther user has no password,
+ // the token can then be activated immediately.
+ AuthenticationToken auth = null;
+ if (!isUserSecure(userId)) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ auth = initializeSyntheticPasswordLocked(null, null,
+ LockPatternUtils.CREDENTIAL_TYPE_NONE, userId);
+ } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
+ long pwdHandle = getSyntheticPasswordHandleLocked(userId);
+ auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
+ pwdHandle, null, userId).authToken;
+ }
+ }
+ disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId);
+ if (auth != null) {
+ mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ }
+ return handle;
+ }
+ }
+
+ private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId);
+ synchronized (mSpManager) {
+ for (long handle : mSpManager.getPendingTokensForUser(userId)) {
+ Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId));
+ mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ }
+ }
+ }
+
+ @Override
+ public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ synchronized (mSpManager) {
+ return mSpManager.existsHandle(handle, userId);
+ }
+ }
+
+ @Override
+ public boolean removeEscrowToken(long handle, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ synchronized (mSpManager) {
+ if (handle == getSyntheticPasswordHandleLocked(userId)) {
+ Slog.w(TAG, "Cannot remove password handle");
+ return false;
+ }
+ if (mSpManager.removePendingToken(handle, userId)) {
+ return true;
+ }
+ if (mSpManager.existsHandle(handle, userId)) {
+ mSpManager.destroyTokenBasedSyntheticPassword(handle, userId);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
+ byte[] token, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ boolean result;
+ synchronized (mSpManager) {
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token,
+ userId);
+ }
+ if (result) {
+ synchronized (mSeparateChallengeLock) {
+ setSeparateProfileChallengeEnabled(userId, true, null);
+ }
+ notifyPasswordChanged(userId);
+ }
+ return result;
+ }
+
+ private boolean setLockCredentialWithTokenInternal(String credential, int type,
+ long tokenHandle, byte[] token, int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
+ getGateKeeperService(), tokenHandle, token, userId);
+ if (result.authToken == null) {
+ Slog.w(TAG, "Invalid escrow token supplied");
+ return false;
+ }
+ long oldHandle = getSyntheticPasswordHandleLocked(userId);
+ setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
+ return true;
+ }
+ }
+
+ @Override
+ public void unlockUserWithToken(long tokenHandle, byte[] token, int userId)
+ throws RemoteException {
+ ensureCallerSystemUid();
+ AuthenticationResult authResult;
+ synchronized (mSpManager) {
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
+ tokenHandle, token, userId);
+ if (authResult.authToken == null) {
+ Slog.w(TAG, "Invalid escrow token supplied");
+ return;
+ }
+ }
+ unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args){
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ pw.println("Permission Denial: can't dump LockSettingsService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (this) {
+ pw.println("Current lock settings service state:");
+ pw.println(String.format("SP Enabled = %b",
+ mLockPatternUtils.isSyntheticPasswordEnabled()));
+
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ final int userId = users.get(user).id;
+ pw.println(" User " + userId);
+ pw.println(String.format(" SP Handle = %x",
+ getSyntheticPasswordHandleLocked(userId)));
+ try {
+ pw.println(String.format(" SID = %x",
+ getGateKeeperService().getSecureUserId(userId)));
+ } catch (RemoteException e) {
+ // ignore.
+ }
+ }
+ }
+ }
+
+ private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // Managed profile should have escrow enabled
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return;
+ }
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ // Devices with Device Owner should have escrow enabled on all users.
+ if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
+ return;
+ }
+ // If the device is yet to be provisioned (still in SUW), there is still
+ // a chance that Device Owner will be set on the device later, so postpone
+ // disabling escrow token for now.
+ if (!dpm.isDeviceProvisioned()) {
+ return;
+ }
+ // Disable escrow token permanently on all other device/user types.
+ Slog.i(TAG, "Disabling escrow token on user " + userId);
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ mSpManager.destroyEscrowData(userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void ensureCallerSystemUid() throws SecurityException {
+ final int callingUid = mInjector.binderGetCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("Only system can call this API.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index 1ab5303..91bd98e 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -22,12 +22,9 @@
import android.app.ActivityManager;
import android.content.Context;
-import android.os.Binder;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -37,6 +34,7 @@
private static final String COMMAND_SET_PIN = "set-pin";
private static final String COMMAND_SET_PASSWORD = "set-password";
private static final String COMMAND_CLEAR = "clear";
+ private static final String COMMAND_SP = "sp";
private int mCurrentUserId;
private final LockPatternUtils mLockPatternUtils;
@@ -71,6 +69,9 @@
case COMMAND_CLEAR:
runClear();
break;
+ case COMMAND_SP:
+ runEnableSp();
+ break;
default:
getErrPrintWriter().println("Unknown command: " + cmd);
break;
@@ -92,6 +93,8 @@
while ((opt = getNextOption()) != null) {
if ("--old".equals(opt)) {
mOld = getNextArgRequired();
+ } else if ("--user".equals(opt)) {
+ mCurrentUserId = Integer.parseInt(getNextArgRequired());
} else {
getErrPrintWriter().println("Unknown option: " + opt);
throw new IllegalArgumentException();
@@ -100,6 +103,15 @@
mNew = getNextArg();
}
+ private void runEnableSp() {
+ if (mNew != null) {
+ mLockPatternUtils.enableSyntheticPassword();
+ getOutPrintWriter().println("Synthetic password enabled");
+ }
+ getOutPrintWriter().println(String.format("SP Enabled = %b",
+ mLockPatternUtils.isSyntheticPasswordEnabled()));
+ }
+
private void runSetPattern() throws RemoteException {
mLockPatternUtils.saveLockPattern(stringToPattern(mNew), mOld, mCurrentUserId);
getOutPrintWriter().println("Pattern set to '" + mNew + "'");
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 385b1cf..f5bae7c 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -66,6 +66,8 @@
private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
+ private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
+
private static final Object DEFAULT = new Object();
private final DatabaseHelper mOpenHelper;
@@ -412,8 +414,7 @@
}
private String getLockCredentialFilePathForUser(int userId, String basename) {
- String dataSystemDirectory =
- android.os.Environment.getDataDirectory().getAbsolutePath() +
+ String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
if (userId == 0) {
// Leave it in the same place for user 0
@@ -423,6 +424,40 @@
}
}
+ public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
+ writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
+ }
+
+ public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
+ return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
+ }
+
+ public void deleteSyntheticPasswordState(int userId, long handle, String name, boolean secure) {
+ String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
+ File file = new File(path);
+ if (file.exists()) {
+ //TODO: (b/34600579) invoke secdiscardable
+ file.delete();
+ mCache.putFile(path, null);
+ }
+ }
+
+ @VisibleForTesting
+ protected File getSyntheticPasswordDirectoryForUser(int userId) {
+ return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
+ }
+
+ @VisibleForTesting
+ protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
+ String name) {
+ File baseDir = getSyntheticPasswordDirectoryForUser(userId);
+ String baseName = String.format("%016x.%s", handle, name);
+ if (!baseDir.exists()) {
+ baseDir.mkdir();
+ }
+ return new File(baseDir, baseName).getAbsolutePath();
+ }
+
public void removeUser(int userId) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -446,15 +481,20 @@
}
}
} else {
- // Manged profile
+ // Managed profile
removeChildProfileLock(userId);
}
+ File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
try {
db.beginTransaction();
db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
db.setTransactionSuccessful();
mCache.removeUser(userId);
+ // The directory itself will be deleted as part of user deletion operation by the
+ // framework, so only need to purge cache here.
+ //TODO: (b/34600579) invoke secdiscardable
+ mCache.purgePath(spStateDir.getAbsolutePath());
} finally {
db.endTransaction();
}
@@ -619,6 +659,16 @@
mVersion++;
}
+ synchronized void purgePath(String path) {
+ for (int i = mCache.size() - 1; i >= 0; i--) {
+ CacheKey entry = mCache.keyAt(i);
+ if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
+ mCache.removeAt(i);
+ }
+ }
+ mVersion++;
+ }
+
synchronized void clear() {
mCache.clear();
mVersion++;
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 3e89852..b33538cb 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -603,7 +603,10 @@
mScanResultKeys = new ArraySet<>(size);
for (int i = 0; i < size; i++) {
ScanResult scanResult = scanResults.get(i);
- mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
+ NetworkKey key = NetworkKey.createFromScanResult(scanResult);
+ if (key != null) {
+ mScanResultKeys.add(key);
+ }
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 32b7e4d..0415971 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2922,10 +2922,10 @@
waitForReady();
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
- // When a user has secure lock screen, require a challenge token to
- // actually unlock. This check is mostly in place for emulation mode.
- if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(token)) {
- throw new IllegalStateException("Token required to unlock secure user " + userId);
+ // When a user has secure lock screen, require secret to actually unlock.
+ // This check is mostly in place for emulation mode.
+ if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) {
+ throw new IllegalStateException("Secret required to unlock secure user " + userId);
}
try {
diff --git a/services/core/java/com/android/server/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/SyntheticPasswordCrypto.java
new file mode 100644
index 0000000..12d91c5
--- /dev/null
+++ b/services/core/java/com/android/server/SyntheticPasswordCrypto.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class SyntheticPasswordCrypto {
+ private static final int PROFILE_KEY_IV_SIZE = 12;
+ private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
+ private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
+ // Time between the user credential is verified with GK and the decryption of synthetic password
+ // under the auth-bound key. This should always happen one after the other, but give it 15
+ // seconds just to be sure.
+ private static final int USER_AUTHENTICATION_VALIDITY = 15;
+
+ private static byte[] decrypt(SecretKey key, byte[] blob)
+ throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ if (blob == null) {
+ return null;
+ }
+ byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
+ byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
+ Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
+ return cipher.doFinal(ciphertext);
+ }
+
+ private static byte[] encrypt(SecretKey key, byte[] blob)
+ throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ if (blob == null) {
+ return null;
+ }
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] ciphertext = cipher.doFinal(blob);
+ byte[] iv = cipher.getIV();
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new RuntimeException("Invalid iv length: " + iv.length);
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(iv);
+ outputStream.write(ciphertext);
+ return outputStream.toByteArray();
+ }
+
+ public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
+ byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ KeyProperties.KEY_ALGORITHM_AES);
+ try {
+ return encrypt(key, message);
+ } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException | IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
+ byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ KeyProperties.KEY_ALGORITHM_AES);
+ try {
+ return decrypt(key, ciphertext);
+ } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException
+ | InvalidAlgorithmParameterException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+
+ SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+ byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
+ return decrypt(decryptionKey, intermediate);
+ } catch (CertificateException | IOException | BadPaddingException
+ | IllegalBlockSizeException
+ | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
+ | InvalidKeyException | UnrecoverableKeyException
+ | InvalidAlgorithmParameterException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed to decrypt blob", e);
+ }
+ }
+
+ public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
+ keyGenerator.init(new SecureRandom());
+ SecretKey secretKey = keyGenerator.generateKey();
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
+ if (sid != 0) {
+ builder.setUserAuthenticationRequired(true)
+ .setBoundToSpecificSecureUserId(sid)
+ .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
+ }
+ keyStore.setEntry(keyAlias,
+ new KeyStore.SecretKeyEntry(secretKey),
+ builder.build());
+ byte[] intermediate = encrypt(secretKey, data);
+ return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
+
+ } catch (CertificateException | IOException | BadPaddingException
+ | IllegalBlockSizeException
+ | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
+ | InvalidKeyException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed to encrypt blob", e);
+ }
+ }
+
+ public static void destroyBlobKey(String keyAlias) {
+ KeyStore keyStore;
+ try {
+ keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry(keyAlias);
+ } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
+ | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
+ try {
+ final int PADDING_LENGTH = 128;
+ MessageDigest digest = MessageDigest.getInstance("SHA-512");
+ if (personalisation.length > PADDING_LENGTH) {
+ throw new RuntimeException("Personalisation too long");
+ }
+ // Personalize the hash
+ // Pad it to the block size of the hash function
+ personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
+ digest.update(personalisation);
+ for (byte[] data : message) {
+ digest.update(data);
+ }
+ return digest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/SyntheticPasswordManager.java b/services/core/java/com/android/server/SyntheticPasswordManager.java
new file mode 100644
index 0000000..6267880
--- /dev/null
+++ b/services/core/java/com/android/server/SyntheticPasswordManager.java
@@ -0,0 +1,692 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import libcore.util.HexEncoding;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+
+/**
+ * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
+ * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
+ * synthetic password blobs which are wrapped by user credentials or escrow tokens.
+ *
+ * Here is the assumptions it makes:
+ * Each user has one single synthetic password at any time.
+ * The SP has an associated password handle, which binds to the SID for that user. The password
+ * handle is persisted by SyntheticPasswordManager internally.
+ * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
+ */
+public class SyntheticPasswordManager {
+ private static final String SP_BLOB_NAME = "spblob";
+ private static final String SP_E0_NAME = "e0";
+ private static final String SP_P1_NAME = "p1";
+ private static final String SP_HANDLE_NAME = "handle";
+ private static final String SECDISCARDABLE_NAME = "secdis";
+ private static final int SECDISCARDABLE_LENGTH = 16 * 1024;
+ private static final String PASSWORD_DATA_NAME = "pwd";
+
+ public static final long DEFAULT_HANDLE = 0;
+ private static final String DEFAULT_PASSWORD = "default-password";
+
+ private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
+ private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
+ private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
+
+ // 256-bit synthetic password
+ private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
+
+ private static final int PASSWORD_SCRYPT_N = 13;
+ private static final int PASSWORD_SCRYPT_R = 3;
+ private static final int PASSWORD_SCRYPT_P = 1;
+ private static final int PASSWORD_SALT_LENGTH = 16;
+ private static final int PASSWORD_TOKEN_LENGTH = 32;
+ private static final String TAG = "SyntheticPasswordManager";
+
+ private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
+ private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
+ private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
+ private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
+ private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
+ private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
+ private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
+
+ static class AuthenticationResult {
+ public AuthenticationToken authToken;
+ public VerifyCredentialResponse gkResponse;
+ }
+
+ static class AuthenticationToken {
+ /*
+ * Here is the relationship between all three fields:
+ * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
+ * syntheticPassword = hash(P0 || P1)
+ * E0 = P0 encrypted under syntheticPassword, stored on disk.
+ */
+ private @Nullable byte[] E0;
+ private @Nullable byte[] P1;
+ private @NonNull String syntheticPassword;
+
+ public String deriveKeyStorePassword() {
+ return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
+ PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
+ }
+
+ public byte[] deriveGkPassword() {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
+ syntheticPassword.getBytes());
+ }
+
+ public byte[] deriveDiskEncryptionKey() {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
+ syntheticPassword.getBytes());
+ }
+
+ private void initialize(byte[] P0, byte[] P1) {
+ this.P1 = P1;
+ this.syntheticPassword = String.valueOf(HexEncoding.encode(
+ SyntheticPasswordCrypto.personalisedHash(
+ PERSONALIZATION_SP_SPLIT, P0, P1)));
+ this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(),
+ PERSONALIZATION_E0, P0);
+ }
+
+ public void recreate(byte[] secret) {
+ initialize(secret, this.P1);
+ }
+
+ protected static AuthenticationToken create() {
+ AuthenticationToken result = new AuthenticationToken();
+ result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
+ secureRandom(SYNTHETIC_PASSWORD_LENGTH));
+ return result;
+ }
+
+ public byte[] computeP0() {
+ if (E0 == null) {
+ return null;
+ }
+ return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
+ E0);
+ }
+ }
+
+ static class PasswordData {
+ byte scryptN;
+ byte scryptR;
+ byte scryptP;
+ public int passwordType;
+ byte[] salt;
+ public byte[] passwordHandle;
+
+ public static PasswordData create(int passwordType) {
+ PasswordData result = new PasswordData();
+ result.scryptN = PASSWORD_SCRYPT_N;
+ result.scryptR = PASSWORD_SCRYPT_R;
+ result.scryptP = PASSWORD_SCRYPT_P;
+ result.passwordType = passwordType;
+ result.salt = secureRandom(PASSWORD_SALT_LENGTH);
+ return result;
+ }
+
+ public static PasswordData fromBytes(byte[] data) {
+ PasswordData result = new PasswordData();
+ ByteBuffer buffer = ByteBuffer.allocate(data.length);
+ buffer.put(data, 0, data.length);
+ buffer.flip();
+ result.passwordType = buffer.getInt();
+ result.scryptN = buffer.get();
+ result.scryptR = buffer.get();
+ result.scryptP = buffer.get();
+ int saltLen = buffer.getInt();
+ result.salt = new byte[saltLen];
+ buffer.get(result.salt);
+ int handleLen = buffer.getInt();
+ result.passwordHandle = new byte[handleLen];
+ buffer.get(result.passwordHandle);
+ return result;
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
+ + Integer.BYTES + salt.length + Integer.BYTES + passwordHandle.length);
+ buffer.putInt(passwordType);
+ buffer.put(scryptN);
+ buffer.put(scryptR);
+ buffer.put(scryptP);
+ buffer.putInt(salt.length);
+ buffer.put(salt);
+ buffer.putInt(passwordHandle.length);
+ buffer.put(passwordHandle);
+ return buffer.array();
+ }
+ }
+
+ private LockSettingsStorage mStorage;
+
+ public SyntheticPasswordManager(LockSettingsStorage storage) {
+ mStorage = storage;
+ }
+
+
+ public int getCredentialType(long handle, int userId) {
+ byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
+ if (passwordData == null) {
+ Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
+ return LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+ return PasswordData.fromBytes(passwordData).passwordType;
+ }
+
+ /**
+ * Initializing a new Authentication token, possibly from an existing credential and hash.
+ *
+ * The authentication token would bear a randomly-generated synthetic password.
+ *
+ * This method has the side effect of rebinding the SID of the given user to the
+ * newly-generated SP.
+ *
+ * If the existing credential hash is non-null, the existing SID mill be migrated so
+ * the synthetic password in the authentication token will produce the same SID
+ * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
+ * in a per-user data storage.
+ *
+ * If the existing credential hash is null, it means the given user should have no SID so
+ * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
+ * the supplied credential parameter is also ignored.
+ *
+ * Also saves the escrow information necessary to re-generate the synthetic password under
+ * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
+ * password escrow should be disabled completely on the given user.
+ *
+ */
+ public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
+ byte[] hash, String credential, int userId) throws RemoteException {
+ AuthenticationToken result = AuthenticationToken.create();
+ GateKeeperResponse response;
+ if (hash != null) {
+ response = gatekeeper.enroll(userId, hash, credential.getBytes(),
+ result.deriveGkPassword());
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
+ clearSidForUser(userId);
+ } else {
+ saveSyntheticPasswordHandle(response.getPayload(), userId);
+ }
+ } else {
+ clearSidForUser(userId);
+ }
+ saveEscrowData(result, userId);
+ return result;
+ }
+
+ /**
+ * Enroll a new password handle and SID for the given synthetic password and persist it on disk.
+ * Used when adding password to previously-unsecured devices.
+ */
+ public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
+ int userId) throws RemoteException {
+ GateKeeperResponse response = gatekeeper.enroll(userId, null, null,
+ authToken.deriveGkPassword());
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.e(TAG, "Fail to create new SID for user " + userId);
+ return;
+ }
+ saveSyntheticPasswordHandle(response.getPayload(), userId);
+ }
+
+ // Nuke the SP handle (and as a result, its SID) for the given user.
+ public void clearSidForUser(int userId) {
+ destroyState(SP_HANDLE_NAME, true, DEFAULT_HANDLE, userId);
+ }
+
+ public boolean hasSidForUser(int userId) {
+ return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ // if null, it means there is no SID associated with the user
+ // This can happen if the user is migrated to SP but currently
+ // do not have a lockscreen password.
+ private byte[] loadSyntheticPasswordHandle(int userId) {
+ return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
+ saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
+ }
+
+ private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
+ authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
+ authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ return authToken.E0 != null && authToken.P1 != null;
+ }
+
+ private void saveEscrowData(AuthenticationToken authToken, int userId) {
+ saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId);
+ saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId);
+ }
+
+ public boolean hasEscrowData(int userId) {
+ return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
+ && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ public void destroyEscrowData(int userId) {
+ destroyState(SP_E0_NAME, true, DEFAULT_HANDLE, userId);
+ destroyState(SP_P1_NAME, true, DEFAULT_HANDLE, userId);
+ }
+
+ /**
+ * Create a new password based SP blob based on the supplied authentication token, such that
+ * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
+ * in the same authentication token.
+ *
+ * This method only creates SP blob wrapping around the given synthetic password and does not
+ * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
+ * is consistent with the device state by calling other APIs in this class.
+ *
+ * @see #newSidForUser
+ * @see #clearSidForUser
+ */
+ public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
+ String credential, int credentialType, AuthenticationToken authToken, int userId)
+ throws RemoteException {
+ if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ credential = DEFAULT_PASSWORD;
+ }
+
+ long handle = generateHandle();
+ PasswordData pwd = PasswordData.create(credentialType);
+ byte[] pwdToken = computePasswordToken(credential, pwd);
+
+ GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null,
+ passwordTokenToGkInput(pwdToken));
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId);
+ return 0;
+ }
+ pwd.passwordHandle = response.getPayload();
+ long sid = sidFromPasswordHandle(pwd.passwordHandle);
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
+
+ byte[] applicationId = transformUnderSecdiscardable(pwdToken,
+ createSecdiscardable(handle, userId));
+ createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
+ applicationId, sid, userId);
+ return handle;
+ }
+
+ private ArrayMap<Integer, ArrayMap<Long, byte[]>> tokenMap = new ArrayMap<>();
+
+ public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
+ long handle = generateHandle();
+ byte[] applicationId = transformUnderSecdiscardable(token,
+ createSecdiscardable(handle, userId));
+ if (!tokenMap.containsKey(userId)) {
+ tokenMap.put(userId, new ArrayMap<>());
+ }
+ tokenMap.get(userId).put(handle, applicationId);
+ return handle;
+ }
+
+ public Set<Long> getPendingTokensForUser(int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return Collections.emptySet();
+ }
+ return tokenMap.get(userId).keySet();
+ }
+
+ public boolean removePendingToken(long handle, int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return false;
+ }
+ return tokenMap.get(userId).remove(handle) != null;
+ }
+
+ public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
+ int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return false;
+ }
+ byte[] applicationId = tokenMap.get(userId).get(handle);
+ if (applicationId == null) {
+ return false;
+ }
+ if (!loadEscrowData(authToken, userId)) {
+ Log.w(TAG, "User is not escrowable");
+ return false;
+ }
+ createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
+ applicationId, 0L, userId);
+ tokenMap.get(userId).remove(handle);
+ return true;
+ }
+
+ private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
+ byte[] applicationId, long sid, int userId) {
+ final byte[] secret;
+ if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ secret = authToken.computeP0();
+ } else {
+ secret = authToken.syntheticPassword.getBytes();
+ }
+ byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
+ byte[] blob = new byte[content.length + 1 + 1];
+ blob[0] = SYNTHETIC_PASSWORD_VERSION;
+ blob[1] = type;
+ System.arraycopy(content, 0, blob, 2, content.length);
+ saveState(SP_BLOB_NAME, blob, handle, userId);
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying the user credential and corresponding password
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
+ long handle, String credential, int userId) throws RemoteException {
+ if (credential == null) {
+ credential = DEFAULT_PASSWORD;
+ }
+ AuthenticationResult result = new AuthenticationResult();
+ PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
+ byte[] pwdToken = computePasswordToken(credential, pwd);
+ byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
+
+ GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
+ pwd.passwordHandle, gkPwdToken);
+ int responseCode = response.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ result.gkResponse = VerifyCredentialResponse.OK;
+ if (response.getShouldReEnroll()) {
+ GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId),
+ pwd.passwordHandle, gkPwdToken, gkPwdToken);
+ if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ pwd.passwordHandle = reenrollResponse.getPayload();
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
+ } else {
+ Log.w(TAG, "Fail to re-enroll user password for user " + userId);
+ // continue the flow anyway
+ }
+ }
+ } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
+ return result;
+ } else {
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+
+
+ byte[] applicationId = transformUnderSecdiscardable(pwdToken,
+ loadSecdiscardable(handle, userId));
+ result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
+ applicationId, userId);
+
+ // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
+ result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ return result;
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying an escrow token and corresponding token
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
+ IGateKeeperService gatekeeper, long handle, byte[] token, int userId)
+ throws RemoteException {
+ AuthenticationResult result = new AuthenticationResult();
+ byte[] applicationId = transformUnderSecdiscardable(token,
+ loadSecdiscardable(handle, userId));
+ result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
+ applicationId, userId);
+ if (result.authToken != null) {
+ result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ if (result.gkResponse == null) {
+ // The user currently has no password. return OK with null payload so null
+ // is propagated to unlockUser()
+ result.gkResponse = VerifyCredentialResponse.OK;
+ }
+ } else {
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ }
+ return result;
+ }
+
+ private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
+ byte[] applicationId, int userId) {
+ byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
+ if (blob == null) {
+ return null;
+ }
+ if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
+ throw new RuntimeException("Unknown blob version");
+ }
+ if (blob[1] != type) {
+ throw new RuntimeException("Invalid blob type");
+ }
+ byte[] secret = decryptSPBlob(getHandleName(handle),
+ Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ if (secret == null) {
+ Log.e(TAG, "Fail to decrypt SP for user " + userId);
+ return null;
+ }
+ AuthenticationToken result = new AuthenticationToken();
+ if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ if (!loadEscrowData(result, userId)) {
+ Log.e(TAG, "User is not escrowable: " + userId);
+ return null;
+ }
+ result.recreate(secret);
+ } else {
+ result.syntheticPassword = new String(secret);
+ }
+ return result;
+ }
+
+ /**
+ * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
+ * if required.
+ *
+ * Normally performing verifyChallenge with an AuthenticationToken should always return
+ * RESPONSE_OK, since user authentication failures are detected earlier when trying to
+ * decrypt SP.
+ */
+ public VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
+ @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
+ byte[] spHandle = loadSyntheticPasswordHandle(userId);
+ if (spHandle == null) {
+ // There is no password handle associated with the given user, i.e. the user is not
+ // secured by lockscreen and has no SID, so just return here;
+ return null;
+ }
+ VerifyCredentialResponse result;
+ GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
+ spHandle, auth.deriveGkPassword());
+ int responseCode = response.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ result = new VerifyCredentialResponse(response.getPayload());
+ if (response.getShouldReEnroll()) {
+ response = gatekeeper.enroll(userId, spHandle,
+ spHandle, auth.deriveGkPassword());
+ if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ spHandle = response.getPayload();
+ saveSyntheticPasswordHandle(spHandle, userId);
+ // Call self again to re-verify with updated handle
+ return verifyChallenge(gatekeeper, auth, challenge, userId);
+ } else {
+ Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
+ // Fall through, return existing handle
+ }
+ }
+ } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ result = new VerifyCredentialResponse(response.getTimeout());
+ } else {
+ result = VerifyCredentialResponse.ERROR;
+ }
+ return result;
+ }
+
+ public boolean existsHandle(long handle, int userId) {
+ return hasState(SP_BLOB_NAME, handle, userId);
+ }
+
+ public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
+ destroySyntheticPassword(handle, userId);
+ destroyState(SECDISCARDABLE_NAME, true, handle, userId);
+ }
+
+ public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
+ destroySyntheticPassword(handle, userId);
+ destroyState(SECDISCARDABLE_NAME, true, handle, userId);
+ destroyState(PASSWORD_DATA_NAME, true, handle, userId);
+ }
+
+ private void destroySyntheticPassword(long handle, int userId) {
+ destroyState(SP_BLOB_NAME, true, handle, userId);
+ destroyState(SP_E0_NAME, true, handle, userId);
+ destroyState(SP_P1_NAME, true, handle, userId);
+ destroySPBlobKey(getHandleName(handle));
+ }
+
+ private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
+ byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
+ PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
+ byte[] result = new byte[data.length + secdiscardable.length];
+ System.arraycopy(data, 0, result, 0, data.length);
+ System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
+ return result;
+ }
+
+ private byte[] createSecdiscardable(long handle, int userId) {
+ byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
+ saveState(SECDISCARDABLE_NAME, data, handle, userId);
+ return data;
+ }
+
+ private byte[] loadSecdiscardable(long handle, int userId) {
+ return loadState(SECDISCARDABLE_NAME, handle, userId);
+ }
+
+ private boolean hasState(String stateName, long handle, int userId) {
+ return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
+ }
+
+ private byte[] loadState(String stateName, long handle, int userId) {
+ return mStorage.readSyntheticPasswordState(userId, handle, stateName);
+ }
+
+ private void saveState(String stateName, byte[] data, long handle, int userId) {
+ mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
+ }
+
+ private void destroyState(String stateName, boolean secure, long handle, int userId) {
+ mStorage.deleteSyntheticPasswordState(userId, handle, stateName, secure);
+ }
+
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
+ }
+
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
+ }
+
+ protected void destroySPBlobKey(String keyAlias) {
+ SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
+ }
+
+ public static long generateHandle() {
+ SecureRandom rng = new SecureRandom();
+ long result;
+ do {
+ result = rng.nextLong();
+ } while (result == DEFAULT_HANDLE);
+ return result;
+ }
+
+ private int fakeUid(int uid) {
+ return 100000 + uid;
+ }
+
+ protected static byte[] secureRandom(int length) {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private String getHandleName(long handle) {
+ return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
+ }
+
+ private byte[] computePasswordToken(String password, PasswordData data) {
+ return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
+ PASSWORD_TOKEN_LENGTH);
+ }
+
+ private byte[] passwordTokenToGkInput(byte[] token) {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
+ }
+
+ protected long sidFromPasswordHandle(byte[] handle) {
+ return nativeSidFromPasswordHandle(handle);
+ }
+
+ protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
+ return nativeScrypt(password.getBytes(), salt, N, r, p, outLen);
+ }
+
+ native long nativeSidFromPasswordHandle(byte[] handle);
+ native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
+
+ final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+ public static String bytesToHex(byte[] bytes) {
+ if (bytes == null) {
+ return "null";
+ }
+ char[] hexChars = new char[bytes.length * 2];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index fbc4440..5c8024a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4087,9 +4087,10 @@
}
@Override
- public void addSharedAccountsFromParentUser(int parentUserId, int userId) {
+ public void addSharedAccountsFromParentUser(int parentUserId, int userId,
+ String opPackageName) {
checkManageOrCreateUsersPermission("addSharedAccountsFromParentUser");
- Account[] accounts = getAccountsAsUser(null, parentUserId, mContext.getOpPackageName());
+ Account[] accounts = getAccountsAsUser(null, parentUserId, opPackageName);
for (Account account : accounts) {
addSharedAccountAsUser(account, userId);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2bc131f..cec5800 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -703,21 +703,27 @@
return false;
}
- public void setServiceForegroundLocked(ComponentName className, IBinder token,
+ public long setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
- setServiceForegroundInnerLocked(r, id, notification, flags);
+ return setServiceForegroundInnerLocked(r, id, notification, flags);
}
+ return ActivityThread.INVALID_PROC_STATE_SEQ;
} finally {
Binder.restoreCallingIdentity(origId);
}
}
- private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
+ /**
+ * @return current process state sequence number {@link UidRecord#curProcStateSeq} corresponding
+ * to the ServiceRecord {@param r} if the calling service has to block until the
+ * network rules are udpated, Otherwise {@link ActivityThread#INVALID_PROC_STATE_SEQ}.
+ */
+ private long setServiceForegroundInnerLocked(ServiceRecord r, int id,
Notification notification, int flags) {
if (id != 0) {
if (notification == null) {
@@ -736,7 +742,7 @@
Slog.w(TAG, "Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground services"
+ ", ignoring.");
- return;
+ return ActivityThread.INVALID_PROC_STATE_SEQ;
case AppOpsManager.MODE_ERRORED:
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground services");
@@ -764,12 +770,18 @@
r.foregroundNoti = notification;
r.isForeground = true;
r.postNotification();
+ long procStateSeqToReturn = ActivityThread.INVALID_PROC_STATE_SEQ;
if (r.app != null) {
updateServiceForegroundLocked(r.app, true);
+ if (r.app.uidRecord != null &&
+ r.app.uidRecord.blockState == ActivityThread.NETWORK_STATE_BLOCK) {
+ procStateSeqToReturn = r.app.uidRecord.curProcStateSeq;
+ }
}
getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
+ return procStateSeqToReturn;
} else {
if (r.isForeground) {
r.isForeground = false;
@@ -790,6 +802,7 @@
}
}
}
+ return ActivityThread.INVALID_PROC_STATE_SEQ;
}
private void cancelForegroudNotificationLocked(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 88e0d03..8ed95ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -63,6 +63,7 @@
static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
static final boolean DEBUG_MU = DEBUG_ALL || false;
+ static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
static final boolean DEBUG_POWER = DEBUG_ALL || false;
@@ -107,6 +108,7 @@
static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
static final String POSTFIX_MU = "_MU";
+ static final String POSTFIX_NETWORK = "_Network";
static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1375c01..c2c24b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -42,6 +42,9 @@
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileRestrictBackgroundOn;
+import static android.net.NetworkPolicyManager.UidStateWithSeqObserver;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
@@ -76,6 +79,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
@@ -105,6 +109,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
@@ -353,6 +358,7 @@
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.firewall.IntentFirewall;
+import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -409,6 +415,7 @@
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
private static final String TAG_LRU = TAG + POSTFIX_LRU;
private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
private static final String TAG_POWER = TAG + POSTFIX_POWER;
private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
@@ -549,7 +556,7 @@
// Intent sent when remote bugreport collection has been completed
private static final String INTENT_REMOTE_BUGREPORT_FINISHED =
- "android.intent.action.REMOTE_BUGREPORT_FINISHED";
+ "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
// Used to indicate that an app transition should be animated.
static final boolean ANIMATE = true;
@@ -595,6 +602,8 @@
BroadcastStats mLastBroadcastStats;
BroadcastStats mCurBroadcastStats;
+ private UidStateWithSeqObserver mUidStateWithSeqObserver;
+
BroadcastQueue broadcastQueueForIntent(Intent intent) {
final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
@@ -4211,6 +4220,25 @@
"*** Delivering " + N + " uid changes");
}
+ if (mUidStateWithSeqObserver != null) {
+ final int registeredCallbackCount = mUidObservers.getRegisteredCallbackCount();
+ for (int i = 0; i < N; ++i) {
+ final UidRecord.ChangeItem item = mActiveUidChanges[i];
+ if (item.change == UidRecord.CHANGE_PROCSTATE) {
+ mUidStateWithSeqObserver.onUidStateChangedWithSeq(
+ item.uid, item.processState, item.procStateSeq);
+ if (VALIDATE_UID_STATES && registeredCallbackCount == 0) {
+ UidRecord validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null) {
+ validateUid = new UidRecord(item.uid);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ validateUid.curProcState = validateUid.setProcState = item.processState;
+ }
+ }
+ }
+ }
+
int i = mUidObservers.beginBroadcast();
while (i > 0) {
i--;
@@ -9595,6 +9623,20 @@
}
@Override
+ public ActivityManager.TaskDescription getTaskDescription(int id) {
+ synchronized (this) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "getTaskDescription()");
+ final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
+ id, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
+ if (tr != null) {
+ return tr.lastTaskDescription;
+ }
+ }
+ return null;
+ }
+
+ @Override
public int addAppTask(IBinder activityToken, Intent intent,
ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException {
final int callingUid = Binder.getCallingUid();
@@ -17573,11 +17615,16 @@
}
}
+ /**
+ * Returns sequence number associated with the current process state change if the service
+ * coming to the foreground needs to block for network before proceeding, otherwise
+ * {@link ActivityThread#INVALID_PROC_STATE_SEQ}.
+ */
@Override
- public void setServiceForeground(ComponentName className, IBinder token,
+ public long setServiceForeground(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
synchronized(this) {
- mServices.setServiceForegroundLocked(className, token, id, notification, flags);
+ return mServices.setServiceForegroundLocked(className, token, id, notification, flags);
}
}
@@ -21338,6 +21385,7 @@
pendingChange.processState = uidRec != null
? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
pendingChange.ephemeral = uidRec.ephemeral;
+ pendingChange.procStateSeq = uidRec.curProcStateSeq;
// Directly update the power manager, since we sit on top of it and it is critical
// it be kept in sync (so wake locks will be held as soon as appropriate).
@@ -21711,6 +21759,34 @@
}
}
+ for (int i = mActiveUids.size() - 1; i >= 0; --i) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ uidRec.shouldNotifyAppThreads = false;
+ if (uidRec.curProcState == uidRec.setProcState) {
+ continue;
+ }
+ final int newBlockState = getUidRecordBlockState(uidRec);
+ // Sequence no. associated with process state change will only be updated if the
+ // process is coming from background to foreground or vice versa.
+ if (newBlockState != uidRec.blockState) {
+ uidRec.blockState = newBlockState;
+ uidRec.curProcStateSeq++;
+ uidRec.appThreadListeners = null;
+ uidRec.shouldNotifyAppThreads = true;
+ }
+ }
+
+ for (int i = mLruProcesses.size() - 1; i >= 0; --i) {
+ final ProcessRecord app = mLruProcesses.get(i);
+ if (!app.killedByAm && app.thread != null && app.uidRecord.shouldNotifyAppThreads) {
+ try {
+ app.thread.setBlockForNetworkState(app.uidRecord.blockState,
+ app.uidRecord.curProcStateSeq);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
mNumServiceProcs = mNewNumServiceProcs;
// Now determine the memory trimming level of background processes.
@@ -22912,6 +22988,131 @@
updateOomAdjLocked(pr);
}
}
+
+ @Override
+ public void setUidStateWithSeqObserver(UidStateWithSeqObserver observer) {
+ synchronized (ActivityManagerService.this) {
+ mUidStateWithSeqObserver = observer;
+ }
+ }
+
+ @Override
+ public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Got update from NPMS uid: " + uid + " seq: " + procStateSeq);
+ }
+ synchronized (ActivityManagerService.this) {
+ final UidRecord record = mActiveUids.get(uid);
+ if (record == null) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
+ + "seq: " + procStateSeq);
+ }
+ return;
+ }
+ record.lastProcStateSeqWithUpdatedNetworkState = procStateSeq;
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Since the current procStateSeq is greater, the "
+ + "listeners would already be notified when it is incremented.");
+ }
+ return;
+ }
+
+ if (record.appThreadListeners == null) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No app thread listeners for uid: " + uid
+ + "seq: " + procStateSeq);
+ }
+ return;
+ }
+ for (int i = record.appThreadListeners.beginBroadcast() - 1; i >= 0; i--) {
+ final IApplicationThread listener =
+ record.appThreadListeners.getBroadcastItem(i);
+ try {
+ if (listener != null) {
+ listener.notifyNetworkStateUpdated(procStateSeq);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+ record.appThreadListeners.finishBroadcast();
+ record.appThreadListeners = null;
+ }
+ }
+ }
+
+ @Override
+ public boolean registerNetworkRulesUpdateListener(IApplicationThread listener,
+ long procStateSeq) {
+ synchronized (this) {
+ final int uid = Binder.getCallingUid();
+ final UidRecord record = mActiveUids.get(uid);
+ if (record.lastProcStateSeqWithUpdatedNetworkState >= procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.v(TAG_NETWORK, "Network state is already updated for seq: " + procStateSeq
+ + ". No need to register listener for uid: " + uid);
+ }
+ return false;
+ }
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.v(TAG_NETWORK, "Since the current procState is greater, there is no need "
+ + " to register listeners for older seq numbers");
+ }
+ }
+ if (record.appThreadListeners == null) {
+ record.appThreadListeners = new RemoteCallbackList<>();
+ }
+ record.appThreadListeners.register(listener);
+ if (DEBUG_NETWORK) {
+ Slog.v(TAG_NETWORK, "Registered listener for uid: " + uid + " seq: " + procStateSeq);
+ }
+ return true;
+ }
+ }
+
+ private int getUidRecordBlockState(UidRecord uidRec) {
+ final boolean curStateAllowedWhileRestrictBackgroundOn
+ = isProcStateAllowedWhileRestrictBackgroundOn(uidRec.curProcState);
+ final boolean curStateAllowedWhileIdleOrPowerSaveMode
+ = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState);
+
+ if (uidRec.setProcState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+ if (uidRec.curProcState != ActivityManager.PROCESS_STATE_UNKNOWN &&
+ (curStateAllowedWhileIdleOrPowerSaveMode
+ || curStateAllowedWhileRestrictBackgroundOn)) {
+ return ActivityThread.NETWORK_STATE_BLOCK;
+ }
+ return ActivityThread.NETWORK_STATE_NO_CHANGE;
+ }
+
+ final boolean prevStateAllowedWhileRestrictBackgroundOn
+ = isProcStateAllowedWhileRestrictBackgroundOn(uidRec.setProcState);
+ final boolean prevStateAllowedWhileIdleOrPowerSaveMode
+ = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState);
+
+ if (prevStateAllowedWhileIdleOrPowerSaveMode == curStateAllowedWhileIdleOrPowerSaveMode &&
+ prevStateAllowedWhileRestrictBackgroundOn ==
+ curStateAllowedWhileRestrictBackgroundOn) {
+ return uidRec.blockState;
+ }
+
+ if (!prevStateAllowedWhileIdleOrPowerSaveMode && curStateAllowedWhileIdleOrPowerSaveMode) {
+ return ActivityThread.NETWORK_STATE_BLOCK;
+ } else if (!prevStateAllowedWhileRestrictBackgroundOn
+ && curStateAllowedWhileRestrictBackgroundOn) {
+ return ActivityThread.NETWORK_STATE_BLOCK;
+ }
+
+ if (prevStateAllowedWhileIdleOrPowerSaveMode && !curStateAllowedWhileIdleOrPowerSaveMode) {
+ return ActivityThread.NETWORK_STATE_UNBLOCK;
+ } else if (prevStateAllowedWhileRestrictBackgroundOn &&
+ !curStateAllowedWhileRestrictBackgroundOn) {
+ return ActivityThread.NETWORK_STATE_UNBLOCK;
+ }
+
+ return uidRec.blockState;
}
private final class SleepTokenImpl extends SleepToken {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 1b19382..082b6b5 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -22,7 +22,6 @@
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;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -47,7 +46,6 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
@@ -85,7 +83,6 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
@@ -290,8 +287,8 @@
/**
* Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
*/
- private final Configuration mTmpGlobalConfig = new Configuration();
- private final Configuration mTmpTaskConfig = new Configuration();
+ private final Configuration mTmpConfig1 = new Configuration();
+ private final Configuration mTmpConfig2 = new Configuration();
private static String startingWindowStateToString(int state) {
switch (state) {
@@ -1975,13 +1972,13 @@
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Ensuring correct configuration: " + this);
- // Short circuit: if the two configurations are equal (the common case), then there is
- // nothing to do.
- final Configuration newGlobalConfig = service.getGlobalConfiguration();
- final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
- if (mLastReportedConfiguration.equals(newGlobalConfig)
- && mLastReportedOverrideConfiguration.equals(newTaskMergedOverrideConfig)
- && !forceNewConfig) {
+ // Short circuit: if the two full configurations are equal (the common case), then there is
+ // nothing to do. We test the full configuration instead of the global and merged override
+ // configurations because there are cases (like moving a task to the pinned stack) where
+ // the combine configurations are equal, but would otherwise differ in the override config
+ mTmpConfig1.setTo(mLastReportedConfiguration);
+ mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
+ if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration unchanged in " + this);
return true;
@@ -1997,14 +1994,16 @@
// Okay we now are going to make this activity have the new config.
// But then we need to figure out how it needs to deal with that.
- mTmpGlobalConfig.setTo(mLastReportedConfiguration);
- mTmpTaskConfig.setTo(mLastReportedOverrideConfiguration);
+ final Configuration newGlobalConfig = service.getGlobalConfiguration();
+ final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+ mTmpConfig1.setTo(mLastReportedConfiguration);
+ mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
mLastReportedConfiguration.setTo(newGlobalConfig);
mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
int taskChanges = getTaskConfigurationChanges(this, newTaskMergedOverrideConfig,
- mTmpTaskConfig);
- final int changes = mTmpGlobalConfig.diff(newGlobalConfig) | taskChanges;
+ mTmpConfig2);
+ final int changes = mTmpConfig1.diff(newGlobalConfig) | taskChanges;
if (changes == 0 && !forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration no differences in " + this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7c24604..f75ce25 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2507,7 +2507,7 @@
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
} else if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
- mStackSupervisor.isFrontStack(lastStack)) {
+ mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
next.showStartingWindow(null /* prev */, false /* newTask */,
false /* taskSwitch */);
}
@@ -4354,7 +4354,7 @@
// If we have a watcher, preflight the move before committing to it. First check
// for *other* available tasks, but if none are available, then try again allowing the
// current task to be selected.
- if (mStackSupervisor.isFrontStack(this) && mService.mController != null) {
+ if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) {
ActivityRecord next = topRunningActivityLocked(null, taskId);
if (next == null) {
next = topRunningActivityLocked(null, 0);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 95734a4..4a29872 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -624,11 +624,6 @@
return stack == mFocusedStack;
}
- /** The top most stack. */
- boolean isFrontStack(ActivityStack stack) {
- return isFrontOfStackList(stack, mHomeStack.mStacks);
- }
-
/** The top most stack on its display. */
boolean isFrontStackOnDisplay(ActivityStack stack) {
return isFrontOfStackList(stack, stack.mActivityContainer.mActivityDisplay.mStacks);
@@ -1103,13 +1098,21 @@
}
// Look in other non-focused and non-home stacks.
- final ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
- if (stack != focusedStack && isFrontStack(stack) && stack.isFocusable()) {
- r = stack.topRunningActivityLocked();
- if (r != null) {
- return r;
+ mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds);
+
+ for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
+ final int displayId = mTmpOrderedDisplayIds.get(i);
+ final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks;
+ if (stacks == null) {
+ continue;
+ }
+ for (int j = stacks.size() - 1; j >= 0; --j) {
+ final ActivityStack stack = stacks.get(j);
+ if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) {
+ r = stack.topRunningActivityLocked();
+ if (r != null) {
+ return r;
+ }
}
}
}
@@ -2676,7 +2679,7 @@
// 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 = isFrontStack(prevStack)
+ final boolean wasFront = isFrontStackOnDisplay(prevStack)
&& (prevStack.topRunningActivityLocked() == r);
if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 73a17c6..9b459d1 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -49,6 +49,7 @@
// add other system settings here...
sGlobalSettingToTypeMap.put(Settings.Global.DEBUG_VIEW_ATTRIBUTES, int.class);
+ sGlobalSettingToTypeMap.put(Settings.Global.WAIT_FOR_NETWORK_TIMEOUT_MS, long.class);
// add other global settings here...
}
@@ -56,6 +57,8 @@
private final ActivityManagerService mActivityManagerService;
+ private static final long WAIT_FOR_NETWORK_TIMEOUT_DEFAULT_MS = 2000; // 2 sec
+
public CoreSettingsObserver(ActivityManagerService activityManagerService) {
super(activityManagerService.mHandler);
mActivityManagerService = activityManagerService;
@@ -143,7 +146,13 @@
} else if (map == sSystemSettingToTypeMap) {
value = Settings.System.getLong(context.getContentResolver(), setting, 0);
} else {
- value = Settings.Global.getLong(context.getContentResolver(), setting, 0);
+ // TODO: remove this conditional and set the default in settings provider.
+ if (Settings.Global.WAIT_FOR_NETWORK_TIMEOUT_MS.equals(setting)) {
+ value = Settings.Global.getLong(context.getContentResolver(), setting,
+ WAIT_FOR_NETWORK_TIMEOUT_DEFAULT_MS);
+ } else {
+ value = Settings.Global.getLong(context.getContentResolver(), setting, 0);
+ }
}
snapshot.putLong(setting, value);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 520d4ee..f8645d6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -35,7 +35,6 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
@@ -52,6 +51,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.wm.AppWindowContainerController;
+import com.android.server.wm.StackWindowController;
import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.TaskWindowContainerListener;
@@ -278,7 +278,6 @@
private final Rect mTmpStableBounds = new Rect();
private final Rect mTmpNonDecorBounds = new Rect();
private final Rect mTmpRect = new Rect();
- private final Rect mTmpRect2 = new Rect();
// Last non-fullscreen bounds the task was launched in or resized to.
// The information is persisted and used to determine the appropriate stack to launch the
@@ -1838,66 +1837,38 @@
return !mTmpConfig.equals(newConfig);
}
- private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
- boolean overrideWidth, boolean overrideHeight) {
- mTmpRect2.set(inInsetBounds);
- mService.mWindowManager.subtractNonDecorInsets(mTmpRect2);
- int leftInset = mTmpRect2.left - inInsetBounds.left;
- int topInset = mTmpRect2.top - inInsetBounds.top;
- int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
- int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
- inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
- }
-
- private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds,
- boolean overrideWidth, boolean overrideHeight) {
- mTmpRect2.set(inInsetBounds);
- mService.mWindowManager.subtractStableInsets(mTmpRect2);
- int leftInset = mTmpRect2.left - inInsetBounds.left;
- int topInset = mTmpRect2.top - inInsetBounds.top;
- int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
- int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
- inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
- }
-
/** Clears passed config and fills it with new override values. */
private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
- final Configuration parentConfig = getParent().getConfiguration();
config.unset();
+ final Configuration parentConfig = getParent().getConfiguration();
final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- final boolean isFloatingTask = mStack != null && StackId.tasksAreFloating(mStack.mStackId);
- if (isFloatingTask) {
- // Floating tasks should not be resized to the screen's bounds.
- config.screenWidthDp = (int) (mTmpStableBounds.width() / density);
- config.screenHeightDp = (int) (mTmpStableBounds.height() / density);
- } else {
- // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
- // i.e. the screen area without the system bars.
- // Additionally task dimensions should not be bigger than its parents dimensions.
- subtractNonDecorInsets(mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds,
- overrideWidth, overrideHeight);
- subtractStableInsets(mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
- overrideWidth, overrideHeight);
- config.screenWidthDp = Math.min(
- (int) (mTmpStableBounds.width() / density), parentConfig.screenWidthDp);
- config.screenHeightDp = Math.min(
- (int) (mTmpStableBounds.height() / density), parentConfig.screenHeightDp);
- }
// TODO: Orientation?
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
? Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
+ if (mStack != null) {
+ final StackWindowController stackController = mStack.getWindowContainerController();
+ stackController.adjustConfigurationForBounds(bounds, insetBounds,
+ mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density,
+ config, parentConfig);
+ } else {
+ // No stack, give some default values
+ config.smallestScreenWidthDp =
+ mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
+ config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
+ Slog.wtf(TAG, "Expected stack when caclulating override config");
+ }
// For calculating screen layout, we need to use the non-decor inset screen area for the
// calculation for compatibility reasons, i.e. screen area without system bars that could
// never go away in Honeycomb.
- final int compatScreenWidthDp = (int)(mTmpNonDecorBounds.width() / density);
- final int compatScreenHeightDp = (int)(mTmpNonDecorBounds.height() / density);
+ final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+ final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
// We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override
// calculation with partial default.
final int sl = Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_XLARGE;
@@ -1905,8 +1876,6 @@
final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
- config.smallestScreenWidthDp = mService.mWindowManager.getSmallestWidthForTaskBounds(
- insetBounds != null ? insetBounds : bounds);
}
/**
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 302f628..1e16bc9 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,8 +17,12 @@
package com.android.server.am;
import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.IApplicationThread;
+import android.os.RemoteCallbackList;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.DebugUtils;
import android.util.TimeUtils;
/**
@@ -34,6 +38,27 @@
boolean setWhitelist;
boolean idle;
int numProcs;
+ /**
+ * Seq no. associated with the current process state change (from background to foreground or
+ * vice versa).
+ */
+ long curProcStateSeq;
+ /**
+ * Latest seq number for which NetworkPolicyManagerService notified ActivityManagerService that
+ * network policy rules are updated.
+ */
+ long lastProcStateSeqWithUpdatedNetworkState;
+ /**
+ * Current block state indicating whether components in the process corresponding to this
+ * uidRecord needs to block for network or unblock or if there is no change.
+ * value will be one of {@link ActivityThread#NETWORK_STATE_BLOCK},
+ * {@link ActivityThread#NETWORK_STATE_UNBLOCK}, {@link ActivityThread#NETWORK_STATE_NO_CHANGE}.
+ */
+ int blockState;
+ /** Indicates whether app threads need be notified of the current blockState change. */
+ boolean shouldNotifyAppThreads;
+ /** Listeners waiting for the network policy rules to get updated. */
+ RemoteCallbackList<IApplicationThread> appThreadListeners;
static final int CHANGE_PROCSTATE = 0;
static final int CHANGE_GONE = 1;
@@ -47,6 +72,7 @@
int change;
int processState;
boolean ephemeral;
+ long procStateSeq;
}
ChangeItem pendingChange;
@@ -83,6 +109,14 @@
}
sb.append(" procs:");
sb.append(numProcs);
+ sb.append(" procStateSeq:");
+ sb.append(curProcStateSeq);
+ sb.append(" lastProcStateSeqWithUpdatedNetworkState:");
+ sb.append(lastProcStateSeqWithUpdatedNetworkState);
+ sb.append(" blockState:");
+ sb.append(DebugUtils.valueToString(ActivityThread.class, "NETWORK_STATE_", blockState));
+ sb.append(" shouldNotifyAppThreads:");
+ sb.append(shouldNotifyAppThreads);
sb.append("}");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index bf1018f..a95a627 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -103,6 +103,10 @@
final boolean change;
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
+ // FIXME SoundPool not ready for state reporting
+ if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
+ }
if (checkConfigurationCaller(piid, apc, binderUid)) {
//TODO add generation counter to only update to the latest state
change = apc.handleStateEvent(event);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 9d63462..6c608a2 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1008,10 +1008,9 @@
return false;
}
- protected boolean requestUpstreamMobileConnection() {
+ protected void requestUpstreamMobileConnection() {
mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
- return true;
}
protected void unrequestUpstreamMobileConnection() {
@@ -1058,9 +1057,13 @@
}
protected void chooseUpstreamType(boolean tryCell) {
+ final int upstreamType = findPreferredUpstreamType(tryCell);
+ setUpstreamByType(upstreamType);
+ }
+
+ protected int findPreferredUpstreamType(boolean tryCell) {
final ConnectivityManager cm = getConnectivityManager();
int upType = ConnectivityManager.TYPE_NONE;
- String iface = null;
updateConfiguration(); // TODO - remove?
@@ -1100,7 +1103,8 @@
requestUpstreamMobileConnection();
break;
case ConnectivityManager.TYPE_NONE:
- if (tryCell && requestUpstreamMobileConnection()) {
+ if (tryCell) {
+ requestUpstreamMobileConnection();
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
@@ -1117,7 +1121,13 @@
break;
}
+ return upType;
+ }
+
+ protected void setUpstreamByType(int upType) {
+ final ConnectivityManager cm = getConnectivityManager();
Network network = null;
+ String iface = null;
if (upType != ConnectivityManager.TYPE_NONE) {
LinkProperties linkProperties = cm.getLinkProperties(upType);
if (linkProperties != null) {
@@ -1354,9 +1364,9 @@
simChange.startListening();
mUpstreamNetworkMonitor.start();
- mTryCell = true; // better try something first pass or crazy tests cases will fail
- chooseUpstreamType(mTryCell);
- mTryCell = !mTryCell;
+ // Better try something first pass or crazy tests cases will fail.
+ chooseUpstreamType(true);
+ mTryCell = false;
}
@Override
@@ -1407,10 +1417,9 @@
break;
}
case CMD_UPSTREAM_CHANGED:
- // need to try DUN immediately if Wifi goes down
- mTryCell = true;
- chooseUpstreamType(mTryCell);
- mTryCell = !mTryCell;
+ // Need to try DUN immediately if Wi-Fi goes down.
+ chooseUpstreamType(true);
+ mTryCell = false;
break;
case CMD_RETRY_UPSTREAM:
chooseUpstreamType(mTryCell);
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 37221a9..5e51579 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -250,31 +250,33 @@
}
private void cleanupUpstream() {
- if (mMyUpstreamIfaceName != null) {
- // note that we don't care about errors here.
- // sometimes interfaces are gone before we get
- // to remove their rules, which generates errors.
- // just do the best we can.
- try {
- // about to tear down NAT; gather remaining statistics
- mStatsService.forceUpdate();
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
- }
- try {
- mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
- } catch (Exception e) {
- if (VDBG) Log.e(
- TAG, "Exception in removeInterfaceForward: " + e.toString());
- }
- try {
- mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
- }
- mMyUpstreamIfaceName = null;
+ if (mMyUpstreamIfaceName == null) return;
+
+ cleanupUpstreamInterface(mMyUpstreamIfaceName);
+ mMyUpstreamIfaceName = null;
+ }
+
+ private void cleanupUpstreamInterface(String upstreamIface) {
+ // Note that we don't care about errors here.
+ // Sometimes interfaces are gone before we get
+ // to remove their rules, which generates errors.
+ // Just do the best we can.
+ try {
+ // About to tear down NAT; gather remaining statistics.
+ mStatsService.forceUpdate();
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
}
- return;
+ try {
+ mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+ }
+ try {
+ mNMService.disableNat(mIfaceName, upstreamIface);
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+ }
}
@Override
@@ -306,6 +308,7 @@
newUpstreamIfaceName);
} catch (Exception e) {
Log.e(TAG, "Exception enabling Nat: " + e.toString());
+ cleanupUpstreamInterface(newUpstreamIfaceName);
mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
transitionTo(mInitialState);
return true;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index a947b41..5f348bf 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -261,6 +261,7 @@
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo = null;
+ mOverrideDisplayInfo = null;
}
}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 7cb223d..1337046 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -448,7 +448,10 @@
mVerb = VERB_STARTING;
scheduleOpTimeOut();
service.startJob(mParams);
- } catch (RemoteException e) {
+ } catch (Exception e) {
+ // We catch 'Exception' because client-app malice or bugs might induce a wide
+ // range of possible exception-throw outcomes from startJob() and its handling
+ // of the client's ParcelableBundle extras.
Slog.e(TAG, "Error sending onStart message to '" +
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 17b005d..8bc72de 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -481,12 +481,6 @@
public void onLost(Network network) {
releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN);
}
-
- @Override
- public void onUnavailable() {
- // timeout, it was not possible to establish the required connection
- releaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
- }
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -877,8 +871,7 @@
NetworkRequest request = requestBuilder.build();
mConnMgr.requestNetwork(
request,
- mSuplConnectivityCallback,
- ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS);
+ mSuplConnectivityCallback);
}
private void handleReleaseSuplConnection(int agpsDataConnStatus) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 98177fe..407262b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -93,7 +93,6 @@
private final SessionManagerImpl mSessionManagerImpl;
private final MediaSessionStack mPriorityStack;
- private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
private final ArrayList<SessionsListenerRecord> mSessionsListeners
= new ArrayList<SessionsListenerRecord>();
@@ -145,7 +144,8 @@
public void updateSession(MediaSessionRecord record) {
synchronized (mLock) {
- if (!mAllSessions.contains(record)) {
+ UserRecord user = mUserRecords.get(record.getUserId());
+ if (user == null || !user.mSessions.contains(record)) {
Log.d(TAG, "Unknown session updated. Ignoring.");
return;
}
@@ -171,7 +171,8 @@
public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
boolean updateSessions = false;
synchronized (mLock) {
- if (!mAllSessions.contains(record)) {
+ UserRecord user = mUserRecords.get(record.getUserId());
+ if (user == null || !user.mSessions.contains(record)) {
Log.d(TAG, "Unknown session changed playback state. Ignoring.");
return;
}
@@ -184,7 +185,8 @@
public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
synchronized (mLock) {
- if (!mAllSessions.contains(record)) {
+ UserRecord user = mUserRecords.get(record.getUserId());
+ if (user == null || !user.mSessions.contains(record)) {
Log.d(TAG, "Unknown session changed playback type. Ignoring.");
return;
}
@@ -318,7 +320,6 @@
}
mPriorityStack.removeSession(session);
- mAllSessions.remove(session);
try {
session.getCallback().asBinder().unlinkToDeath(session, 0);
@@ -455,7 +456,6 @@
throw new RuntimeException("Media Session owner died prematurely.", e);
}
- mAllSessions.add(session);
mPriorityStack.addSession(session, mCurrentUserIdList.contains(userId));
user.addSessionLocked(session);
@@ -1087,16 +1087,10 @@
synchronized (mLock) {
pw.println(mSessionsListeners.size() + " sessions listeners.");
- int count = mAllSessions.size();
- pw.println(count + " Sessions:");
- for (int i = 0; i < count; i++) {
- mAllSessions.get(i).dump(pw, "");
- pw.println();
- }
mPriorityStack.dump(pw, "");
pw.println("User Records:");
- count = mUserRecords.size();
+ int count = mUserRecords.size();
for (int i = 0; i < count; i++) {
UserRecord user = mUserRecords.get(mUserRecords.keyAt(i));
user.dumpLocked(pw, "");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index ac3a025..91c9316 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.app.ActivityThread.INVALID_PROC_STATE_SEQ;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
@@ -53,11 +54,15 @@
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
+import static android.net.NetworkPolicyManager.RULE_INVALID;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
+import static android.net.NetworkPolicyManager.UidStateWithSeqObserver;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
+import static android.net.NetworkPolicyManager.isProcStateAllowedWhileRestrictBackgroundOn;
import static android.net.NetworkPolicyManager.uidPoliciesToString;
import static android.net.NetworkPolicyManager.uidRulesToString;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
@@ -94,6 +99,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -123,6 +129,7 @@
import android.net.NetworkIdentity;
import android.net.NetworkInfo;
import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkTemplate;
@@ -130,6 +137,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
+import android.os.Debug;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -161,8 +169,10 @@
import android.util.NtpTrustedTime;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TrustedTime;
import android.util.Xml;
@@ -210,14 +220,16 @@
* enforcement.
*
* <p>
- * This class uses 2-3 locks to synchronize state:
+ * This class uses 4 locks to synchronize state:
* <ul>
* <li>{@code mUidRulesFirstLock}: used to guard state related to individual UIDs (such as firewall
* rules).
* <li>{@code mNetworkPoliciesSecondLock}: used to guard state related to network interfaces (such
* as network policies).
- * <li>{@code allLocks}: not a "real" lock, but an indication (through @GuardedBy) that all locks
- * must be held.
+ * <li>{@code mDispatchedThirdLock}: used to guard state related to process state sequence numbers
+ * of uids which are currently blocked waiting for network.
+ * <li>{@code allLocks}: not a "real" lock, but an indication (through @GuardedBy) that both locks
+ * {@code mUidRulesFirstLock} and {@code mNetworkPoliciesSecondLock} must be held.
* </ul>
*
* <p>
@@ -225,8 +237,11 @@
* <ul>
* <li>{@code UL()}: require the "UID" lock ({@code mUidRulesFirstLock}).
* <li>{@code NL()}: require the "Network" lock ({@code mNetworkPoliciesSecondLock}).
- * <li>{@code AL()}: require all locks, which must be obtained in order ({@code mUidRulesFirstLock}
- * first, then {@code mNetworkPoliciesSecondLock}, then {@code mYetAnotherGuardThirdLock}, etc..
+ * <li>{@code DL()}: require the "Dispatched" lock ({@code mDispatchedThirdLock}).
+ * <li>{@code AL()}: require both locks {@code mUidRulesFirstLock} and
+ * {@code mNetworkPoliciesSecondLock}.
+ * When multiple locks are needed, they must be obtained in order ({@code mUidRulesFirstLock}
+ * first, then {@code mNetworkPoliciesSecondLock}, then {@code mDispatchedThirdLock}, etc..
* </ul>
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@@ -313,6 +328,27 @@
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
+ final Object mDispatchedThirdLock = new Object();
+
+ @GuardedBy("mDispatchedThirdLock")
+ private final SparseLongArray mLastHandledProcStateSeq = new SparseLongArray();
+
+ /**
+ * Used for tracking whether the updated uid and firewall rules have been dispatched to
+ * ConnectivityService and NetworkManagementService respectively.
+ *
+ * SparseIntArray: uid -> dispatch flags (one or more combinations of {@link #FLAG_NONE},
+ * {@link #FLAG_UID_RULES_DISPATCHED}, {@link #FLAG_FIREWALL_RULES_DISPATCHED} and
+ * {@link #FLAG_ALL_RULES_DISPATCHED}).
+ */
+ @GuardedBy("mDispatchedThirdLock")
+ private final SparseIntArray mDispatchFlagsForCurProcStateSeq = new SparseIntArray();
+
+ private final int FLAG_NONE = 0;
+ private final int FLAG_UID_RULES_DISPATCHED = 1 << 0;
+ private final int FLAG_FIREWALL_RULES_DISPATCHED = 1 << 1;
+ private final int FLAG_ALL_RULES_DISPATCHED =
+ (FLAG_UID_RULES_DISPATCHED | FLAG_FIREWALL_RULES_DISPATCHED);
@GuardedBy("allLocks") volatile boolean mSystemReady;
@@ -407,6 +443,8 @@
private final IPackageManager mIPm;
+ private ActivityManagerInternal mActivityManagerInternal;
+
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -617,13 +655,16 @@
try {
mActivityManager.registerUidObserver(mUidObserver,
- ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.UID_OBSERVER_GONE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mActivityManagerInternal.setUidStateWithSeqObserver(mUidStateWithSeqObserver);
+
// listen for changes to power save whitelist
final IntentFilter whitelistFilter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
@@ -705,17 +746,24 @@
}
}
- final private IUidObserver mUidObserver = new IUidObserver.Stub() {
- @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+ final private UidStateWithSeqObserver mUidStateWithSeqObserver = new UidStateWithSeqObserver() {
+ @Override
+ public void onUidStateChangedWithSeq(int uid, int procState, long procStateSeq) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
try {
+ final long effectiveProcStateSeq = getEffectiveProcStateSeq(uid, procStateSeq);
synchronized (mUidRulesFirstLock) {
- updateUidStateUL(uid, procState);
+ updateUidStateUL(uid, procState, effectiveProcStateSeq);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
+ };
+
+ final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+ }
@Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
synchronized (mUidRulesFirstLock) {
@@ -1842,7 +1890,7 @@
}
// uid policy changed, recompute rules and persist policy.
- updateRulesForDataUsageRestrictionsUL(uid);
+ updateRulesForDataUsageRestrictionsUL(uid, true);
if (persist) {
synchronized (mNetworkPoliciesSecondLock) {
writePolicyAL();
@@ -2441,7 +2489,7 @@
private boolean isUidForegroundOnRestrictBackgroundUL(int uid) {
final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
- return isProcStateAllowedWhileOnRestrictBackground(procState);
+ return isProcStateAllowedWhileRestrictBackgroundOn(procState);
}
private boolean isUidForegroundOnRestrictPowerUL(int uid) {
@@ -2459,26 +2507,50 @@
* {@link #updateRulesForDataUsageRestrictionsUL(int)} and
* {@link #updateRulesForPowerRestrictionsUL(int)}
*/
- private void updateUidStateUL(int uid, int uidState) {
+ private void updateUidStateUL(int uid, int uidState, long procStateSeq) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL");
try {
final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (oldUidState != uidState) {
// state changed, push updated rules
mUidState.put(uid, uidState);
- updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, uidState);
- if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
- != isProcStateAllowedWhileIdleOrPowerSaveMode(uidState) ) {
- if (isUidIdle(uid)) {
- updateRuleForAppIdleUL(uid);
+ if (procStateSeq != INVALID_PROC_STATE_SEQ) {
+ int updatedUidRules = RULE_INVALID;
+ ReturnStatus status = updateRestrictBackgroundRulesOnUidStatusChangedUL(
+ uid, oldUidState, uidState, false);
+ if (status != null && status.mNeedToNotify) {
+ updatedUidRules = status.mNewUidRules;
}
- if (mDeviceIdleMode) {
- updateRuleForDeviceIdleUL(uid);
+ final boolean procStateChangedAllowedWhileIdleOrPowerSaveMode =
+ isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
+ != isProcStateAllowedWhileIdleOrPowerSaveMode(uidState);
+ if (procStateChangedAllowedWhileIdleOrPowerSaveMode) {
+ status = updateRulesForPowerRestrictionsUL(uid, false);
+ if (status != null && status.mNeedToNotify) {
+ updatedUidRules = status.mNewUidRules;
+ }
}
- if (mRestrictPower) {
- updateRuleForRestrictPowerUL(uid);
+ // TODO: We can avoid this if the rules are not changed. But since dispatching
+ // to ConnectivityService is currently asynchronous, we need this to make sure
+ // any previous the msg_rules_changes have been handled. Optimize this once
+ // dispatching from NPMS to ConnectivityService is made synchronous.
+ mHandler.obtainMessage(MSG_RULES_CHANGED, uid, updatedUidRules,
+ procStateSeq).sendToTarget();
+ if (procStateChangedAllowedWhileIdleOrPowerSaveMode) {
+ if (isUidIdle(uid)) {
+ updateRuleForAppIdleUL(uid);
+ }
+ if (mDeviceIdleMode) {
+ updateRuleForDeviceIdleUL(uid);
+ }
+ if (mRestrictPower) {
+ updateRuleForRestrictPowerUL(uid);
+ }
}
- updateRulesForPowerRestrictionsUL(uid);
+ synchronized (mDispatchedThirdLock) {
+ setDispatchedFlagDL(uid, procStateSeq, FLAG_FIREWALL_RULES_DISPATCHED);
+ checkAndNotifyDL(uid, procStateSeq);
+ }
}
updateNetworkStats(uid, isUidStateForegroundUL(uidState));
}
@@ -2487,6 +2559,61 @@
}
}
+ /**
+ * Returns {@link android.app.ActivityThread#INVALID_PROC_STATE_SEQ} if acting on
+ * {@param procStateSeq} leads to an invalid state, otherwise update global state and return
+ * {@param procStateSeq}.
+ */
+ private long getEffectiveProcStateSeq(int uid, long procStateSeq) {
+ synchronized (mDispatchedThirdLock) {
+ final long lastHandledProcStateSeq = mLastHandledProcStateSeq.get(uid);
+ if (procStateSeq < lastHandledProcStateSeq) {
+ Slog.wtf(TAG, "procStateSeq from AMS should never go down, procStateSeq: "
+ + procStateSeq + " lastHandledProcStateSeq: " + lastHandledProcStateSeq
+ + " uid: " + uid);
+ return INVALID_PROC_STATE_SEQ;
+ }
+ if (procStateSeq == lastHandledProcStateSeq) {
+ if (LOGD) {
+ Slog.d(TAG, "procStateSeq: " + procStateSeq + " is not changed, so process is "
+ + "not jumping from background to foreground or vice versa. "
+ + "uid: " + uid);
+ }
+ return INVALID_PROC_STATE_SEQ;
+ }
+ mLastHandledProcStateSeq.put(uid, procStateSeq);
+ mDispatchFlagsForCurProcStateSeq.put(uid, 0);
+ return procStateSeq;
+ }
+ }
+
+ /**
+ * Update dispatch flags to include {@param flag}.
+ */
+ private void setDispatchedFlagDL(int uid, long procStateSeq, int flag) {
+ int dispatchedFlag = mDispatchFlagsForCurProcStateSeq.get(uid);
+ dispatchedFlag |= flag;
+ mDispatchFlagsForCurProcStateSeq.put(uid, dispatchedFlag);
+ }
+
+ /**
+ * Check whether uid and firewall rules are dispatched to ConnectivityService and
+ * NetworkManagementService respectively, if so notify ActivityManagerService that network
+ * rules are updated.
+ */
+ private void checkAndNotifyDL(int uid, long procStateSeq) {
+ synchronized (mDispatchedThirdLock) {
+ final int dispatchedFlags = mDispatchFlagsForCurProcStateSeq.get(uid);
+ if (dispatchedFlags == FLAG_ALL_RULES_DISPATCHED) {
+ if (LOGD) {
+ Slog.d(TAG, "Notifying AMS that network rules are updated for uid: " + uid
+ + " seq: " + procStateSeq + " callers: " + Debug.getCallers(3));
+ }
+ mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
+ }
+ }
+ }
+
private void removeUidStateUL(int uid) {
final int index = mUidState.indexOfKey(uid);
if (index >= 0) {
@@ -2494,17 +2621,21 @@
mUidState.removeAt(index);
if (oldUidState != ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState,
- ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ ActivityManager.PROCESS_STATE_CACHED_EMPTY, true);
if (mDeviceIdleMode) {
updateRuleForDeviceIdleUL(uid);
}
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
- updateRulesForPowerRestrictionsUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, true);
updateNetworkStats(uid, false);
}
}
+ synchronized (mDispatchedThirdLock) {
+ mLastHandledProcStateSeq.delete(uid);
+ mDispatchFlagsForCurProcStateSeq.delete(uid);
+ }
}
// adjust stats accounting based on foreground status
@@ -2516,23 +2647,16 @@
}
}
- private void updateRestrictBackgroundRulesOnUidStatusChangedUL(int uid, int oldUidState,
- int newUidState) {
+ private ReturnStatus updateRestrictBackgroundRulesOnUidStatusChangedUL(int uid, int oldUidState,
+ int newUidState, boolean notify) {
final boolean oldForeground =
- isProcStateAllowedWhileOnRestrictBackground(oldUidState);
+ isProcStateAllowedWhileRestrictBackgroundOn(oldUidState);
final boolean newForeground =
- isProcStateAllowedWhileOnRestrictBackground(newUidState);
+ isProcStateAllowedWhileRestrictBackgroundOn(newUidState);
if (oldForeground != newForeground) {
- updateRulesForDataUsageRestrictionsUL(uid);
+ return updateRulesForDataUsageRestrictionsUL(uid, notify);
}
- }
-
- static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
- return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- }
-
- static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
- return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ return null;
}
void updateRulesForPowerSaveUL() {
@@ -2681,7 +2805,7 @@
// Skip if it had no restrictions to begin with
if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
}
- updateRulesForPowerRestrictionsUL(uid, oldRules, paroled);
+ updateRulesForPowerRestrictionsUL(uid, oldRules, paroled, true);
}
}
@@ -2761,10 +2885,10 @@
final int uid = UserHandle.getUid(user.id, app.uid);
switch (type) {
case TYPE_RESTRICT_BACKGROUND:
- updateRulesForDataUsageRestrictionsUL(uid);
+ updateRulesForDataUsageRestrictionsUL(uid, true);
break;
case TYPE_RESTRICT_POWER:
- updateRulesForPowerRestrictionsUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, true);
break;
default:
Slog.w(TAG, "Invalid type for updateRulesForAllApps: " + type);
@@ -2790,7 +2914,7 @@
updateRuleForDeviceIdleUL(uid);
updateRuleForRestrictPowerUL(uid);
// Update internal rules.
- updateRulesForPowerRestrictionsUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, true);
}
}
}
@@ -2854,6 +2978,10 @@
mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
mPowerSaveWhitelistAppIds.delete(uid);
mPowerSaveTempWhitelistAppIds.delete(uid);
+ synchronized (mDispatchedThirdLock) {
+ mLastHandledProcStateSeq.delete(uid);
+ mDispatchFlagsForCurProcStateSeq.delete(uid);
+ }
// ...then update iptables asynchronously.
mHandler.obtainMessage(MSG_RESET_FIREWALL_RULES_BY_UID, uid, 0).sendToTarget();
@@ -2879,10 +3007,10 @@
updateRuleForRestrictPowerUL(uid);
// Update internal state for power-related modes.
- updateRulesForPowerRestrictionsUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, true);
// Update firewall and internal rules for Data Saver Mode.
- updateRulesForDataUsageRestrictionsUL(uid);
+ updateRulesForDataUsageRestrictionsUL(uid, true);
}
/**
@@ -2923,11 +3051,16 @@
*
* <p>The {@link #mUidRules} map is used to define the transtion of states of an UID.
*
+ * @param uid The uid for which the rules have to be updated.
+ * @param notify Indicates whether to notify network policy listeners if the rules are updated.
+ *
+ * @return ReturnStatus includes new updated rules and whether network policy listeners
+ * (INetworkPolicyListener) need to be notified.
*/
- private void updateRulesForDataUsageRestrictionsUL(int uid) {
+ private ReturnStatus updateRulesForDataUsageRestrictionsUL(int uid, boolean notify) {
if (!isUidValidForWhitelistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
- return;
+ return new ReturnStatus(false, RULE_NONE);
}
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
@@ -3022,9 +3155,12 @@
+ ", oldRule=" + uidRulesToString(oldUidRules));
}
- // Dispatch changed rule to existing listeners.
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ if (notify) {
+ mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ }
+ return new ReturnStatus(true, newUidRules);
}
+ return new ReturnStatus(false, newUidRules);
}
/**
@@ -3045,16 +3181,18 @@
* <p>
* <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
*/
- private void updateRulesForPowerRestrictionsUL(int uid) {
+ private ReturnStatus updateRulesForPowerRestrictionsUL(int uid, boolean notify) {
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false);
+ final ReturnStatus status = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false,
+ notify);
- if (newUidRules == RULE_NONE) {
+ if (status.mNewUidRules == RULE_NONE) {
mUidRules.delete(uid);
} else {
- mUidRules.put(uid, newUidRules);
+ mUidRules.put(uid, status.mNewUidRules);
}
+ return status;
}
/**
@@ -3063,13 +3201,17 @@
* @param uid the uid of the app to update rules for
* @param oldUidRules the current rules for the uid, in order to determine if there's a change
* @param paroled whether to ignore idle state of apps and only look at other restrictions.
+ * @param notify whether to notify network policy listeners (INetworkPolicyListener) if the
+ * rules are updated.
*
- * @return the new computed rules for the uid
+ * @return ReturnStatus includes new updated rules and whether network policy listeners
+ * (INetworkPolicyListener) need to be notified.
*/
- private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+ private ReturnStatus updateRulesForPowerRestrictionsUL(int uid, int oldUidRules,
+ boolean paroled, boolean notify) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
- return RULE_NONE;
+ return new ReturnStatus(false, RULE_NONE);
}
final boolean isIdle = !paroled && isUidIdle(uid);
@@ -3121,10 +3263,23 @@
+ ", newRule=" + uidRulesToString(newUidRules)
+ ", oldRule=" + uidRulesToString(oldUidRules));
}
- mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ if (notify) {
+ mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ }
+ return new ReturnStatus(true, newUidRules);
}
- return newUidRules;
+ return new ReturnStatus(false, newUidRules);
+ }
+
+ private static final class ReturnStatus {
+ boolean mNeedToNotify;
+ int mNewUidRules;
+
+ ReturnStatus(boolean needToNotify, int newUidRules) {
+ mNeedToNotify = needToNotify;
+ mNewUidRules = newUidRules;
+ }
}
private class AppIdleStateChangeListener
@@ -3138,7 +3293,7 @@
if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
synchronized (mUidRulesFirstLock) {
updateRuleForAppIdleUL(uid);
- updateRulesForPowerRestrictionsUL(uid);
+ updateRulesForPowerRestrictionsUL(uid, true);
}
} catch (NameNotFoundException nnfe) {
}
@@ -3198,13 +3353,26 @@
case MSG_RULES_CHANGED: {
final int uid = msg.arg1;
final int uidRules = msg.arg2;
- dispatchUidRulesChanged(mConnectivityListener, uid, uidRules);
- final int length = mListeners.beginBroadcast();
- for (int i = 0; i < length; i++) {
- final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- dispatchUidRulesChanged(listener, uid, uidRules);
+ if (uidRules != RULE_INVALID) {
+ dispatchUidRulesChanged(mConnectivityListener, uid, uidRules);
}
- mListeners.finishBroadcast();
+ final Long procStateSeq = (Long) msg.obj;
+ if (procStateSeq != null) {
+ synchronized (mDispatchedThirdLock) {
+ if (mLastHandledProcStateSeq.get(uid) == procStateSeq) {
+ setDispatchedFlagDL(uid, procStateSeq, FLAG_UID_RULES_DISPATCHED);
+ checkAndNotifyDL(uid, procStateSeq);
+ }
+ }
+ }
+ if (uidRules != RULE_INVALID) {
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ dispatchUidRulesChanged(listener, uid, uidRules);
+ }
+ mListeners.finishBroadcast();
+ }
return true;
}
case MSG_METERED_IFACES_CHANGED: {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 19c9d9b..f2b5564 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -121,6 +121,7 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeProto;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -140,6 +141,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.util.FastXmlSerializer;
@@ -2458,12 +2460,14 @@
Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
return null;
}
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- writePolicyXml(baos, true /*forBackup*/);
- return baos.toByteArray();
- } catch (IOException e) {
- Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+ synchronized(mPolicyFile) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writePolicyXml(baos, true /*forBackup*/);
+ return baos.toByteArray();
+ } catch (IOException e) {
+ Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+ }
}
return null;
}
@@ -2481,12 +2485,14 @@
Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
return;
}
- final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
- try {
- readPolicyXml(bais, true /*forRestore*/);
- savePolicyFile();
- } catch (NumberFormatException | XmlPullParserException | IOException e) {
- Slog.w(TAG, "applyRestore: error reading payload", e);
+ synchronized(mPolicyFile) {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+ try {
+ readPolicyXml(bais, true /*forRestore*/);
+ savePolicyFile();
+ } catch (NumberFormatException | XmlPullParserException | IOException e) {
+ Slog.w(TAG, "applyRestore: error reading payload", e);
+ }
}
}
@@ -2808,8 +2814,26 @@
proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED);
}
}
+ List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
+ N = snoozed.size();
+ if (N > 0) {
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord nr = snoozed.get(i);
+ if (filter.filtered && !filter.matches(nr.sbn)) continue;
+ nr.dump(proto, filter.redact);
+ proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED);
+ }
+ }
proto.end(records);
}
+
+ long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+ mZenModeHelper.dump(proto);
+ for (ComponentName suppressor : mEffectsSuppressors) {
+ proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+ }
+ proto.end(zenLog);
+
proto.flush();
}
@@ -2895,24 +2919,12 @@
}
pw.println(" ");
}
+
+ mSnoozeHelper.dump(pw, filter);
}
}
if (!zenOnly) {
- pw.println("\n Usage Stats:");
- mUsageStats.dump(pw, " ", filter);
- }
-
- if (!filter.filtered || zenOnly) {
- pw.println("\n Zen Mode:");
- pw.print(" mInterruptionFilter="); pw.println(mInterruptionFilter);
- mZenModeHelper.dump(pw, " ");
-
- pw.println("\n Zen Log:");
- ZenLog.dump(pw, " ");
- }
-
- if (!zenOnly) {
pw.println("\n Ranking Config:");
mRankingHelper.dump(pw, " ", filter);
@@ -2941,8 +2953,13 @@
mNotificationAssistants.dump(pw, filter);
}
- if (!zenOnly) {
- mSnoozeHelper.dump(pw, filter);
+ if (!filter.filtered || zenOnly) {
+ pw.println("\n Zen Mode:");
+ pw.print(" mInterruptionFilter="); pw.println(mInterruptionFilter);
+ mZenModeHelper.dump(pw, " ");
+
+ pw.println("\n Zen Log:");
+ ZenLog.dump(pw, " ");
}
pw.println("\n Policy access:");
@@ -2960,6 +2977,11 @@
r.dump(pw, " ", getContext(), filter.redact);
}
}
+
+ if (!zenOnly) {
+ pw.println("\n Usage Stats:");
+ mUsageStats.dump(pw, " ", filter);
+ }
}
}
@@ -3127,7 +3149,9 @@
// snoozed apps
if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
- // TODO: log to event log
+ MetricsLogger.action(r.getLogMaker()
+ .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
if (DBG) {
Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
}
@@ -3863,14 +3887,18 @@
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
final String canceledKey = r.getKey();
- // Remove from either list
- boolean wasPosted;
- if (mNotificationList.remove(r)) {
- mNotificationsByKey.remove(r.sbn.getKey());
+ // Remove from both lists, either list could have a separate Record for what is effectively
+ // the same notification.
+ boolean wasPosted = false;
+ NotificationRecord recordInList = null;
+ if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) != null) {
+ mNotificationList.remove(recordInList);
+ mNotificationsByKey.remove(recordInList.sbn.getKey());
wasPosted = true;
- } else {
- mEnqueuedNotifications.remove(r);
- wasPosted = false;
+ }
+ if ((recordInList = findNotificationByListLocked(mEnqueuedNotifications, r.getKey()))
+ != null) {
+ mEnqueuedNotifications.remove(recordInList);
}
// Record caller.
@@ -4151,7 +4179,7 @@
if (until < System.currentTimeMillis() && snoozeCriterionId == null) {
return;
}
- // TODO: write to event log
+
if (DBG) {
Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId,
listenerName));
@@ -4163,6 +4191,11 @@
synchronized (mNotificationLock) {
final NotificationRecord r = findNotificationByKeyLocked(key);
if (r != null) {
+ MetricsLogger.action(r.getLogMaker()
+ .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
+ .setType(MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
+ snoozeCriterionId == null ? false : true));
cancelNotificationLocked(r, false, REASON_SNOOZED);
updateLightsLocked();
if (snoozeCriterionId != null) {
@@ -4181,7 +4214,6 @@
void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
- // TODO: write to event log
if (DBG) {
Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName));
}
@@ -4294,17 +4326,12 @@
// TODO: need to combine a bunch of these getters with slightly different behavior.
// TODO: Should enqueuing just add to mNotificationsByKey instead?
private NotificationRecord findNotificationByKeyLocked(String key) {
- final int N = mNotificationList.size();
- for (int i = 0; i < N; i++) {
- if (key.equals(mNotificationList.get(i).getKey())) {
- return mNotificationList.get(i);
- }
+ NotificationRecord r;
+ if ((r = findNotificationByListLocked(mNotificationList, key)) != null) {
+ return r;
}
- final int M = mEnqueuedNotifications.size();
- for (int i = 0; i < M; i++) {
- if (key.equals(mEnqueuedNotifications.get(i).getKey())) {
- return mEnqueuedNotifications.get(i);
- }
+ if ((r = findNotificationByListLocked(mEnqueuedNotifications, key)) != null) {
+ return r;
}
return null;
}
@@ -4322,8 +4349,7 @@
}
private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
- String pkg, String tag, int id, int userId)
- {
+ String pkg, String tag, int id, int userId) {
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
@@ -4335,6 +4361,18 @@
return null;
}
+ private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
+ String key)
+ {
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ if (key.equals(list.get(i).getKey())) {
+ return list.get(i);
+ }
+ }
+ return null;
+ }
+
// lock on mNotificationList
int indexOfNotificationLocked(String key) {
final int N = mNotificationList.size();
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index f2aff11..0cd8cea 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -16,11 +16,14 @@
package com.android.server.notification;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -99,7 +102,7 @@
return Collections.EMPTY_LIST;
}
- protected List<NotificationRecord> getSnoozed() {
+ protected @NonNull List<NotificationRecord> getSnoozed() {
List<NotificationRecord> snoozedForUser = new ArrayList<>();
int[] userIds = mUserProfiles.getCurrentProfileIds();
final int N = userIds.length;
@@ -270,6 +273,9 @@
final NotificationRecord record = pkgRecords.remove(key);
if (record != null) {
+ MetricsLogger.action(record.getLogMaker()
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
+ .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
mCallback.repost(userId, record);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 66fb976..75190f3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -50,14 +50,18 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings.Global;
+import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
+import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenModeConfig.ZenRule;
+import android.service.notification.ZenModeProto;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
@@ -488,6 +492,24 @@
}
}
+ void dump(ProtoOutputStream proto) {
+
+ proto.write(ZenModeProto.ZEN_MODE, mZenMode);
+ synchronized (mConfig) {
+ if (mConfig.manualRule != null) {
+ proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, mConfig.manualRule.toString());
+ }
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.enabled && rule.condition.state == Condition.STATE_TRUE
+ && !rule.snoozing) {
+ proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
+ }
+ }
+ proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString());
+ proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mZenMode=");
pw.println(Global.zenModeToString(mZenMode));
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 9da94b3..06b6f66 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.app.DownloadManager;
import android.app.admin.DevicePolicyManager;
+import android.companion.CompanionDeviceManager;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -673,6 +674,16 @@
&& doesPackageSupportRuntimePermissions(storageManagerPckg)) {
grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
}
+
+ // Companion devices
+ PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr(
+ CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
+ if (companionDeviceDiscoveryPackage != null
+ && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
+ grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage,
+ LOCATION_PERMISSIONS, true, userId);
+ }
+
mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 179f310..96a2577 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -515,9 +515,6 @@
if (!canAccessProfile(callingPackage, targetUserId, "Cannot start activity")) {
return false;
}
- if (!canAccessProfile(callingPackage, targetUserId, "Cannot access shortcuts")) {
- return false;
- }
if (!isUserEnabled(targetUserId)) {
throw new IllegalStateException("Cannot start a shortcut for disabled profile "
+ targetUserId);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index db712ae..b589057 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -66,6 +66,9 @@
public static final int DEX_OPT_PERFORMED = 1;
public static final int DEX_OPT_FAILED = -1;
+ /** Special library name that skips shared libraries check during compilation. */
+ public static final String SKIP_SHARED_LIBRARY_CHECK = "&";
+
private final Installer mInstaller;
private final Object mInstallLock;
@@ -274,7 +277,7 @@
// TODO(calin): maybe add a separate call.
mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
/*oatDir*/ null, dexoptFlags,
- compilerFilter, info.volumeUuid, /*sharedLibrariesPath*/ null);
+ compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK);
}
return DEX_OPT_PERFORMED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ebd0b34..f43e468 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -126,6 +126,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AppsQueryHelper;
+import android.content.pm.ChangedPackages;
import android.content.pm.ComponentInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.EphemeralRequest;
@@ -544,9 +545,6 @@
public static final int REASON_LAST = REASON_CORE_APP;
- /** Special library name that skips shared libraries check during compilation. */
- private static final String SKIP_SHARED_LIBRARY_CHECK = "&";
-
/** All dangerous permission names in the same order as the events in MetricsEvent */
private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
Manifest.permission.READ_CALENDAR,
@@ -720,6 +718,21 @@
private final InstantAppRegistry mInstantAppRegistry;
+ @GuardedBy("mPackages")
+ int mChangedPackagesSequenceNumber;
+ /**
+ * List of changed [installed, removed or updated] packages.
+ * mapping from user id -> sequence number -> package name
+ */
+ @GuardedBy("mPackages")
+ final SparseArray<SparseArray<String>> mChangedPackages = new SparseArray<>();
+ /**
+ * The sequence number of the last change to a package.
+ * mapping from user id -> package name -> sequence number
+ */
+ @GuardedBy("mPackages")
+ final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+
public static final class SharedLibraryEntry {
public final String path;
public final String apk;
@@ -2141,6 +2154,7 @@
pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM);
}
}
+ scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM);
}
}
@@ -2384,7 +2398,7 @@
DEXOPT_PUBLIC,
getCompilerFilterForReason(REASON_SHARED_APK),
StorageManager.UUID_PRIVATE_INTERNAL,
- SKIP_SHARED_LIBRARY_CHECK);
+ PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + libPath);
@@ -4220,6 +4234,52 @@
}
}
+ private void updateSequenceNumberLP(String packageName, int[] userList) {
+ for (int i = userList.length - 1; i >= 0; --i) {
+ final int userId = userList[i];
+ SparseArray<String> changedPackages = mChangedPackages.get(userId);
+ if (changedPackages == null) {
+ changedPackages = new SparseArray<>();
+ mChangedPackages.put(userId, changedPackages);
+ }
+ Map<String, Integer> sequenceNumbers = mChangedPackagesSequenceNumbers.get(userId);
+ if (sequenceNumbers == null) {
+ sequenceNumbers = new HashMap<>();
+ mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
+ }
+ final Integer sequenceNumber = sequenceNumbers.get(packageName);
+ if (sequenceNumber != null) {
+ changedPackages.remove(sequenceNumber);
+ }
+ changedPackages.put(mChangedPackagesSequenceNumber, packageName);
+ sequenceNumbers.put(packageName, mChangedPackagesSequenceNumber);
+ }
+ mChangedPackagesSequenceNumber++;
+ }
+
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber, int userId) {
+ synchronized (mPackages) {
+ if (sequenceNumber >= mChangedPackagesSequenceNumber) {
+ return null;
+ }
+ final SparseArray<String> changedPackages = mChangedPackages.get(userId);
+ if (changedPackages == null) {
+ return null;
+ }
+ final List<String> packageNames =
+ new ArrayList<>(mChangedPackagesSequenceNumber - sequenceNumber);
+ for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
+ final String packageName = changedPackages.get(i);
+ if (packageName != null) {
+ packageNames.add(packageName);
+ }
+ }
+ return packageNames.isEmpty()
+ ? null : new ChangedPackages(mChangedPackagesSequenceNumber, packageNames);
+ }
+ }
+
@Override
public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() {
ArrayList<FeatureInfo> res;
@@ -13251,6 +13311,7 @@
pkgSetting.setHidden(false, userId);
pkgSetting.setInstallReason(installReason, userId);
mSettings.writePackageRestrictionsLPr(userId);
+ mSettings.writeKernelMappingLPr(pkgSetting);
installed = true;
}
}
@@ -13263,6 +13324,9 @@
}
}
sendPackageAddedForUser(packageName, pkgSetting, userId);
+ synchronized (mPackages) {
+ updateSequenceNumberLP(packageName, new int[]{ userId });
+ }
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -16345,6 +16409,7 @@
//note that the new package setting would have already been
//added to mPackages. It hasn't been persisted yet.
mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
+ // TODO: Remove this write? It's also written at the end of this method
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
mSettings.writeLPr();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -16418,6 +16483,7 @@
} else if (!previousUserIds.contains(userId)) {
ps.setInstallReason(installReason, userId);
}
+ mSettings.writeKernelMappingLPr(ps);
}
res.name = pkgName;
res.uid = newPackage.applicationInfo.uid;
@@ -16847,6 +16913,10 @@
sUserManager.getUserIds(), true);
}
}
+
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ updateSequenceNumberLP(pkgName, res.newUsers);
+ }
}
}
@@ -17429,6 +17499,7 @@
if (res) {
mInstantAppRegistry.onPackageUninstalledLPw(uninstalledPs.pkg,
info.removedUsers);
+ updateSequenceNumberLP(packageName, info.removedUsers);
}
}
}
@@ -17597,6 +17668,7 @@
// writer
synchronized (mPackages) {
+ boolean installedStateChanged = false;
if (deletedPs != null) {
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL);
@@ -17644,6 +17716,9 @@
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
+ if (installed != ps.getInstalled(userId)) {
+ installedStateChanged = true;
+ }
ps.setInstalled(installed, userId);
}
}
@@ -17653,6 +17728,9 @@
// Save settings now
mSettings.writeLPr();
}
+ if (installedStateChanged) {
+ mSettings.writeKernelMappingLPr(ps);
+ }
}
if (removedAppId != -1) {
// A user ID was deleted here. Go through all users and remove it
@@ -17795,6 +17873,7 @@
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
if (applyUserRestrictions) {
+ boolean installedStateChanged = false;
if (DEBUG_REMOVE) {
Slog.d(TAG, "Propagating install state across reinstall");
}
@@ -17803,6 +17882,9 @@
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
+ if (installed != ps.getInstalled(userId)) {
+ installedStateChanged = true;
+ }
ps.setInstalled(installed, userId);
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
@@ -17810,6 +17892,9 @@
// Regardless of writeSettings we need to ensure that this restriction
// state propagation is persisted
mSettings.writeAllUsersPackageRestrictionsLPr();
+ if (installedStateChanged) {
+ mSettings.writeKernelMappingLPr(ps);
+ }
}
// can downgrade to reader here
if (writeSettings) {
@@ -18013,6 +18098,7 @@
// broadcasts will be sent correctly.
if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
ps.setInstalled(true, user.getIdentifier());
+ mSettings.writeKernelMappingLPr(ps);
}
} else {
// This is a system app, so we assume that the
@@ -18122,6 +18208,7 @@
ps.readUserState(nextUserId).domainVerificationStatus, 0,
PackageManager.INSTALL_REASON_UNKNOWN);
}
+ mSettings.writeKernelMappingLPr(ps);
}
private boolean clearPackageStateForUserLIF(PackageSetting ps, int userId,
@@ -19725,6 +19812,7 @@
}
}
scheduleWritePackageRestrictionsLocked(userId);
+ updateSequenceNumberLP(packageName, new int[] { userId });
components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index b63edfd..0e11b0c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -40,6 +40,9 @@
* Settings base class for pending and resolved classes.
*/
abstract class PackageSettingBase extends SettingBase {
+
+ private static final int[] EMPTY_INT_ARRAY = new int[0];
+
/**
* Indicates the state of installation. Used by PackageManager to figure out
* incomplete installations. Say a package is being installed (the state is
@@ -502,6 +505,25 @@
userState.delete(userId);
}
+ public int[] getNotInstalledUserIds() {
+ int count = 0;
+ int userStateCount = userState.size();
+ for (int i = 0; i < userStateCount; i++) {
+ if (userState.valueAt(i).installed == false) {
+ count++;
+ }
+ }
+ if (count == 0) return EMPTY_INT_ARRAY;
+ int[] excludedUserIds = new int[count];
+ int idx = 0;
+ for (int i = 0; i < userStateCount; i++) {
+ if (userState.valueAt(i).installed == false) {
+ excludedUserIds[idx++] = userState.keyAt(i);
+ }
+ }
+ return excludedUserIds;
+ }
+
IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
return verificationInfo;
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 2781150..7e7de21 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -72,6 +72,9 @@
// Append ephemeral to existing seinfo label
private static final String EPHEMERAL_APP_STR = ":ephemeralapp";
+ // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
+ private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
+
/**
* Load the mac_permissions.xml file containing all seinfo assignments used to
* label apps. The loaded mac_permissions.xml file is determined by the
@@ -296,6 +299,8 @@
if (pkg.applicationInfo.isPrivilegedApp())
pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
+ pkg.applicationInfo.seinfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
+
if (DEBUG_POLICY_INSTALL) {
Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
"seinfo=" + pkg.applicationInfo.seinfo);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 281e445..6156802 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -252,6 +252,7 @@
private final File mPackageListFilename;
private final File mStoppedPackagesFilename;
private final File mBackupStoppedPackagesFilename;
+ /** The top level directory in configfs for sdcardfs to push the package->uid,userId mappings */
private final File mKernelMappingFilename;
/** Map from package name to settings */
@@ -260,8 +261,8 @@
/** List of packages that installed other packages */
final ArraySet<String> mInstallerPackages = new ArraySet<>();
- /** Map from package name to appId */
- private final ArrayMap<String, Integer> mKernelMapping = new ArrayMap<>();
+ /** Map from package name to appId and excluded userids */
+ private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>();
// List of replaced system applications
private final ArrayMap<String, PackageSetting> mDisabledSysPackages =
@@ -271,6 +272,11 @@
private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
new ArrayMap<String, IntentFilterVerificationInfo>();
+ private static final class KernelPackageState {
+ int appId;
+ int[] excludedUserIds;
+ }
+
// Bookkeeping for restored user permission grants
final class RestoredPermissionGrant {
String permissionName;
@@ -2512,6 +2518,15 @@
//Debug.stopMethodTracing();
}
+ private void writeKernelRemoveUserLPr(int userId) {
+ if (mKernelMappingFilename == null) return;
+
+ File removeUserIdFile = new File(mKernelMappingFilename, "remove_userid");
+ if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + userId + " to " + removeUserIdFile
+ .getAbsolutePath());
+ writeIntToFile(removeUserIdFile, userId);
+ }
+
void writeKernelMappingLPr() {
if (mKernelMappingFilename == null) return;
@@ -2538,27 +2553,63 @@
}
void writeKernelMappingLPr(PackageSetting ps) {
- if (mKernelMappingFilename == null) return;
+ if (mKernelMappingFilename == null || ps == null || ps.name == null) return;
- final Integer cur = mKernelMapping.get(ps.name);
- if (cur != null && cur.intValue() == ps.appId) {
- // Ignore when mapping already matches
- return;
+ KernelPackageState cur = mKernelMapping.get(ps.name);
+ final boolean firstTime = cur == null;
+ int[] excludedUserIds = ps.getNotInstalledUserIds();
+ final boolean userIdsChanged = firstTime
+ || !Arrays.equals(excludedUserIds, cur.excludedUserIds);
+
+ // Package directory
+ final File dir = new File(mKernelMappingFilename, ps.name);
+
+ if (firstTime) {
+ dir.mkdir();
+ // Create a new mapping state
+ cur = new KernelPackageState();
+ mKernelMapping.put(ps.name, cur);
}
- if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId);
+ // If mapping is incorrect or non-existent, write the appid file
+ if (cur.appId != ps.appId) {
+ final File appIdFile = new File(dir, "appid");
+ writeIntToFile(appIdFile, ps.appId);
+ if (DEBUG_KERNEL) Slog.d(TAG, "Mapping " + ps.name + " to " + ps.appId);
+ }
- final File dir = new File(mKernelMappingFilename, ps.name);
- dir.mkdir();
+ if (userIdsChanged) {
+ // Build the exclusion list -- the ids to add to the exclusion list
+ for (int i = 0; i < excludedUserIds.length; i++) {
+ if (cur.excludedUserIds == null || !ArrayUtils.contains(cur.excludedUserIds,
+ excludedUserIds[i])) {
+ writeIntToFile(new File(dir, "excluded_userids"), excludedUserIds[i]);
+ if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + excludedUserIds[i] + " to "
+ + ps.name + "/excluded_userids");
+ }
+ }
+ // Build the inclusion list -- the ids to remove from the exclusion list
+ if (cur.excludedUserIds != null) {
+ for (int i = 0; i < cur.excludedUserIds.length; i++) {
+ if (!ArrayUtils.contains(excludedUserIds, cur.excludedUserIds[i])) {
+ writeIntToFile(new File(dir, "clear_userid"),
+ cur.excludedUserIds[i]);
+ if (DEBUG_KERNEL) Slog.d(TAG, "Writing " + cur.excludedUserIds[i] + " to "
+ + ps.name + "/clear_userid");
- final File file = new File(dir, "appid");
+ }
+ }
+ }
+ cur.excludedUserIds = excludedUserIds;
+ }
+ }
+
+ private void writeIntToFile(File file, int value) {
try {
- // Note that the use of US_ASCII here is safe, we're only writing a decimal
- // number to the file.
FileUtils.bytesToFile(file.getAbsolutePath(),
- Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII));
- mKernelMapping.put(ps.name, ps.appId);
+ Integer.toString(value).getBytes(StandardCharsets.US_ASCII));
} catch (IOException ignored) {
+ Slog.w(TAG, "Couldn't write " + value + " to " + file.getAbsolutePath());
}
}
@@ -4081,6 +4132,9 @@
!ArrayUtils.contains(disallowedPackages, ps.name);
// Only system apps are initially installed.
ps.setInstalled(shouldInstall, userHandle);
+ if (!shouldInstall) {
+ writeKernelMappingLPr(ps);
+ }
// Need to create a data directory for all apps under this user. Accumulate all
// required args and call the installer after mPackages lock has been released
volumeUuids[i] = ps.volumeUuid;
@@ -4123,6 +4177,10 @@
mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
writePackageListLPr();
+
+ // Inform kernel that the user was removed, so that packages are marked uninstalled
+ // for sdcardfs
+ writeKernelRemoveUserLPr(userId);
}
void removeCrossProfileIntentFiltersLPw(int userId) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 3085c9c..570259b 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1581,6 +1581,11 @@
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
+ if (si.hasMaskableBitmap() && !si.hasIconFile()) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " has maskable bitmap but was not saved to a file.");
+ }
if (si.hasIconFile() && si.hasIconResource()) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index d8857b7..057e781 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1216,7 +1216,8 @@
// he XML we'd lose the icon. We just remove all dangling files after saving the XML.
shortcut.setIconResourceId(0);
shortcut.setIconResName(null);
- shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
+ shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
+ ShortcutInfo.FLAG_MASKABLE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES);
}
public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
@@ -1351,7 +1352,8 @@
shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
return;
}
- case Icon.TYPE_BITMAP: {
+ case Icon.TYPE_BITMAP:
+ case Icon.TYPE_BITMAP_MASKABLE: {
bitmap = icon.getBitmap(); // Don't recycle in this case.
break;
}
@@ -1382,6 +1384,9 @@
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);
+ }
} finally {
IoUtils.closeQuietly(out);
}
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
index b704eb1..3c73c88 100644
--- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -20,7 +20,7 @@
import java.io.File;
import java.io.IOException;
-import libcore.tzdata.update2.TimeZoneBundleInstaller;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
/**
* An install receiver responsible for installing timezone data updates.
@@ -34,14 +34,14 @@
private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
private static final String UPDATE_VERSION_FILE_NAME = "version";
- private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+ private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip";
- private final TimeZoneBundleInstaller installer;
+ private final TimeZoneDistroInstaller installer;
public TzDataInstallReceiver() {
super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
UPDATE_VERSION_FILE_NAME);
- installer = new TimeZoneBundleInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
+ installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2c315445..8f38be8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -919,6 +919,10 @@
void updateDisplayInfo() {
mDisplay.getDisplayInfo(mDisplayInfo);
mDisplay.getMetrics(mDisplayMetrics);
+
+ // Check if display metrics changed and update base values if needed.
+ updateBaseDisplayMetricsIfNeeded();
+
for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
mTaskStackContainers.get(i).updateDisplayInfo(null);
}
@@ -934,10 +938,8 @@
}
}
- mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
- mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
- mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+ mDisplayInfo.logicalDensityDpi);
}
void getLogicalDisplayRect(Rect out) {
@@ -967,6 +969,30 @@
}
}
+ /** If display metrics changed and it's not just a rotation - update base values. */
+ private void updateBaseDisplayMetricsIfNeeded() {
+ final int orientation = mDisplayInfo.rotation;
+ final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+ final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth;
+ final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight;
+
+ boolean displayMetricsChanged
+ = mBaseDisplayWidth != newWidth || mBaseDisplayHeight != newHeight;
+ displayMetricsChanged |= mBaseDisplayDensity != mDisplayInfo.logicalDensityDpi;
+
+ if (displayMetricsChanged) {
+ updateBaseDisplayMetrics(newWidth, newHeight, mDisplayInfo.logicalDensityDpi);
+ mService.reconfigureDisplayLocked(this);
+ }
+ }
+
+ void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+ mBaseDisplayWidth = mInitialDisplayWidth = baseWidth;
+ mBaseDisplayHeight = mInitialDisplayHeight = baseHeight;
+ mBaseDisplayDensity = mInitialDisplayDensity = baseDensity;
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ }
+
void getContentRect(Rect out) {
out.set(mContentRect);
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 0a92a81..75a79fd 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -152,11 +152,6 @@
int getSmallestWidthDpForBounds(Rect bounds) {
final DisplayInfo di = mDisplayContent.getDisplayInfo();
- // If the bounds are fullscreen, return the value of the fullscreen configuration
- if (bounds == null || (bounds.left == 0 && bounds.top == 0
- && bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
- return mDisplayContent.getConfiguration().smallestScreenWidthDp;
- }
final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
int minWidth = Integer.MAX_VALUE;
@@ -185,7 +180,7 @@
mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
mTmpRect3);
- mService.subtractInsets(mTmpRect2, mTmpRect3, mTmpRect);
+ mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect);
minWidth = Math.min(mTmpRect.width(), minWidth);
}
return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index a872ea4..6a8417dc 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -105,6 +105,7 @@
private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
private final Rect mTmpInsets = new Rect();
private final Rect mTmpRect = new Rect();
+ private final Point mTmpDisplaySize = new Point();
/**
* The callback object passed to listeners for them to notify the controller of state changes.
@@ -209,6 +210,9 @@
final int top = (int) (stackBounds.centerY() - height / 2f);
stackBounds.set(left, top, left + width, top + height);
mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+ if (mIsMinimized) {
+ applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
+ }
return stackBounds;
}
@@ -269,11 +273,7 @@
mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction);
if (mIsMinimized) {
- final Point displaySize = new Point(mDisplayInfo.logicalWidth,
- mDisplayInfo.logicalHeight);
- mService.getStableInsetsLocked(displayContent.getDisplayId(), mStableInsets);
- mSnapAlgorithm.applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds,
- displaySize, mStableInsets);
+ applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
}
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
}
@@ -387,6 +387,16 @@
}
/**
+ * Applies the minimized offsets to the given stack bounds.
+ */
+ private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
+ mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+ mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mStableInsets);
+ mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
+ mStableInsets);
+ }
+
+ /**
* @return the pixels for a given dp value.
*/
private int dpToPx(float dpValue, DisplayMetrics dm) {
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 36d07e0..142f69a 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+
+import android.app.ActivityManager.StackId;
import android.app.RemoteAction;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -24,6 +27,8 @@
import android.os.Message;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.DisplayInfo;
+
import com.android.server.UiThread;
import com.android.internal.annotations.VisibleForTesting;
@@ -48,6 +53,12 @@
private final H mHandler;
+ // Temp bounds only used in adjustConfigurationForBounds()
+ private final Rect mTmpRect = new Rect();
+ private final Rect mTmpStableInsets = new Rect();
+ private final Rect mTmpNonDecorInsets = new Rect();
+ private final Rect mTmpDisplayBounds = new Rect();
+
public StackWindowController(int stackId, StackWindowListener listener,
int displayId, boolean onTop, Rect outBounds) {
this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
@@ -289,6 +300,107 @@
}
}
+ /**
+ * Adjusts the screen size in dp's for the {@param config} for the given params.
+ */
+ public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
+ Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
+ boolean overrideHeight, float density, Configuration config,
+ Configuration parentConfig) {
+ synchronized (mWindowMap) {
+ final TaskStack stack = mContainer;
+ final DisplayContent displayContent = stack.getDisplayContent();
+ final DisplayInfo di = displayContent.getDisplayInfo();
+
+ // Get the insets and display bounds
+ mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ mTmpStableInsets);
+ mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ mTmpNonDecorInsets);
+ mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
+
+ int width;
+ int height;
+ if (StackId.tasksAreFloating(mStackId)) {
+ // Floating tasks should not be resized to the screen's bounds.
+
+ if (bounds.width() == mTmpDisplayBounds.width() &&
+ bounds.height() == mTmpDisplayBounds.height()) {
+ // If the bounds we are animating is the same as the fullscreen stack
+ // dimensions, then apply the same inset calculations that we normally do for
+ // the fullscreen stack, without intersecting it with the display bounds
+ stableBounds.inset(mTmpStableInsets);
+ nonDecorBounds.inset(mTmpNonDecorInsets);
+ }
+ width = (int) (stableBounds.width() / density);
+ height = (int) (stableBounds.height() / density);
+ } else {
+ // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+ // area, i.e. the screen area without the system bars.
+ // Additionally task dimensions should not be bigger than its parents dimensions.
+ // The non decor inset are areas that could never be removed in Honeycomb. See
+ // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+ intersectDisplayBoundsExcludeInsets(nonDecorBounds,
+ insetBounds != null ? insetBounds : bounds, mTmpNonDecorInsets,
+ mTmpDisplayBounds, overrideWidth, overrideHeight);
+ intersectDisplayBoundsExcludeInsets(stableBounds,
+ insetBounds != null ? insetBounds : bounds, mTmpStableInsets,
+ mTmpDisplayBounds, overrideWidth, overrideHeight);
+ width = Math.min((int) (stableBounds.width() / density),
+ parentConfig.screenWidthDp);
+ height = Math.min((int) (stableBounds.height() / density),
+ parentConfig.screenHeightDp);
+ }
+
+ config.screenWidthDp = width;
+ config.screenHeightDp = height;
+ config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
+ insetBounds != null ? insetBounds : bounds, density);
+ }
+ }
+
+ /**
+ * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
+ * inset areas.
+ *
+ * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
+ */
+ private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
+ Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
+ mTmpRect.set(inInsetBounds);
+ mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
+ int leftInset = mTmpRect.left - inInsetBounds.left;
+ int topInset = mTmpRect.top - inInsetBounds.top;
+ int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
+ int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
+ inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
+ }
+
+ /**
+ * Calculates the smallest width for a task given the {@param bounds}.
+ *
+ * @return the smallest width to be used in the Configuration, in dips
+ */
+ private int getSmallestWidthForTaskBounds(Rect bounds, float density) {
+ final DisplayContent displayContent = mContainer.getDisplayContent();
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+ if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
+ bounds.height() == displayInfo.logicalHeight)) {
+ // If the bounds are fullscreen, return the value of the fullscreen configuration
+ return displayContent.getConfiguration().smallestScreenWidthDp;
+ } else if (StackId.tasksAreFloating(mStackId)) {
+ // For floating tasks, calculate the smallest width from the bounds of the task
+ return (int) (Math.min(bounds.width(), bounds.height()) / density);
+ } else {
+ // Iterating across all screen orientations, and return the minimum of the task
+ // width taking into account that the bounds might change because the snap algorithm
+ // snaps to a different value
+ return displayContent.getDockedDividerController()
+ .getSmallestWidthDpForBounds(bounds);
+ }
+ }
+
void requestResize(Rect bounds) {
mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 544d1e3..b9429f4 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -423,8 +423,12 @@
mBoundsAfterRotation.setEmpty();
final DockedStackDividerController controller = getDisplayContent()
.mDividerControllerLocked;
- if (controller.isMinimizedDock() && mStackId == DOCKED_STACK_ID) {
- outTempBounds.set(controller.getMiddlePositionDockedStackRect());
+ if (mStackId == DOCKED_STACK_ID) {
+ final Rect dockedStackRect = controller.getMiddlePositionDockedStackRect();
+
+ if (dockedStackRect != null) {
+ outTempBounds.set(dockedStackRect);
+ }
}
}
@@ -1472,7 +1476,10 @@
@Override
public void getFullScreenBounds(Rect bounds) {
- getDisplayContent().getContentRect(bounds);
+ // This is currently only used for the pinned stack animation when leaving PiP
+ // (see {@link BoundsAnimationController}), and in that case we need to animate this back
+ // to the full bounds to match the fullscreen stack
+ getDisplayContent().getLogicalDisplayRect(bounds);
}
public boolean hasMovementAnimations() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index eae08b3..5653113 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -75,6 +75,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -5931,8 +5932,8 @@
if (displayContent.mBaseDisplayWidth != width
|| displayContent.mBaseDisplayHeight != height) {
Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
+ displayContent.updateBaseDisplayMetrics(width, height,
+ displayContent.mBaseDisplayDensity);
}
} catch (NumberFormatException ex) {
}
@@ -5957,8 +5958,7 @@
// displayContent must not be null
private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
+ displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
reconfigureDisplayLocked(displayContent);
}
@@ -6146,6 +6146,7 @@
* Get an array with display ids ordered by focus priority - last items should be given
* focus first. Sparse array just maps position to displayId.
*/
+ // TODO: Maintain display list in focus order in ActivityManager and remove this call.
public void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) {
synchronized(mWindowMap) {
mRoot.getDisplaysInFocusOrder(displaysInFocusOrder);
@@ -7543,63 +7544,12 @@
}
}
- private void getNonDecorInsetsLocked(Rect outInsets) {
- final DisplayInfo di = getDefaultDisplayInfoLocked();
- mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, outInsets);
- }
-
- /**
- * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
- * inset areas.
- *
- * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
- */
- public void subtractStableInsets(Rect inOutBounds) {
- synchronized (mWindowMap) {
- getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect2);
- final DisplayInfo di = getDefaultDisplayInfoLocked();
- mTmpRect.set(0, 0, di.logicalWidth, di.logicalHeight);
- subtractInsets(mTmpRect, mTmpRect2, inOutBounds);
- }
- }
-
- /**
- * Intersects the specified {@code inOutBounds} with the display frame that excludes
- * areas that could never be removed in Honeycomb. See
- * {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- *
- * @param inOutBounds The inOutBounds to subtract the inset areas from.
- */
- public void subtractNonDecorInsets(Rect inOutBounds) {
- synchronized (mWindowMap) {
- getNonDecorInsetsLocked(mTmpRect2);
- final DisplayInfo di = getDefaultDisplayInfoLocked();
- mTmpRect.set(0, 0, di.logicalWidth, di.logicalHeight);
- subtractInsets(mTmpRect, mTmpRect2, inOutBounds);
- }
- }
-
- void subtractInsets(Rect display, Rect insets, Rect inOutBounds) {
+ void intersectDisplayInsetBounds(Rect display, Rect insets, Rect inOutBounds) {
mTmpRect3.set(display);
mTmpRect3.inset(insets);
inOutBounds.intersect(mTmpRect3);
}
- /**
- * Calculates the smallest width for a task given the {@param bounds}. It does that by iterating
- * across all screen orientations, and returns the minimum of the task width taking into account
- * that the bounds might change because the snap algorithm snaps to a different value.
- *
- * @return the smallest width to be used in the Configuration, in dips
- */
- public int getSmallestWidthForTaskBounds(Rect bounds) {
- synchronized (mWindowMap) {
- // TODO(multi-display): Use correct display content here
- return getDefaultDisplayContentLocked().getDockedDividerController()
- .getSmallestWidthDpForBounds(bounds);
- }
- }
-
MousePositionTracker mMousePositionTracker = new MousePositionTracker();
private static class MousePositionTracker implements PointerEventListener {
@@ -7914,6 +7864,17 @@
}
@Override
+ public void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+ boolean imeWindowVisible, @Nullable IBinder targetWindowToken) {
+ // TODO (b/34628091): Use this method to address the window animation issue.
+ if (DEBUG_INPUT_METHOD) {
+ Slog.w(TAG_WM, "updateInputMethodWindowStatus: imeToken=" + imeToken
+ + " imeWindowVisible=" + imeWindowVisible
+ + " targetWindowToken=" + targetWindowToken);
+ }
+ }
+
+ @Override
public boolean isHardKeyboardAvailable() {
synchronized (mWindowMap) {
return mHardKeyboardAvailable;
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index eab5d8a..2c3cda5 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -19,6 +19,7 @@
$(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
$(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvUinputBridge.cpp \
@@ -33,12 +34,14 @@
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
+ external/scrypt/lib/crypto \
frameworks/base/services \
frameworks/base/libs \
frameworks/base/libs/hwui \
frameworks/base/core/jni \
frameworks/native/services \
system/core/libappfuse/include \
+ system/gatekeeper/include \
system/security/keystore/include \
$(call include-path-for, libhardware)/hardware \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
@@ -50,6 +53,7 @@
libappfuse \
libbinder \
libcutils \
+ libcrypto \
liblog \
libhardware \
libhardware_legacy \
@@ -83,3 +87,5 @@
android.hardware.tv.input@1.0 \
android.hardware.vibrator@1.0 \
android.hardware.vr@1.0 \
+
+LOCAL_STATIC_LIBRARIES += libscrypt_static
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_SyntheticPasswordManager.cpp
new file mode 100644
index 0000000..a9f7b9f
--- /dev/null
+++ b/services/core/jni/com_android_server_SyntheticPasswordManager.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "SyntheticPasswordManager"
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+#include <android_runtime/Log.h>
+#include <utils/Timers.h>
+#include <utils/misc.h>
+#include <utils/String8.h>
+#include <utils/Log.h>
+#include <gatekeeper/password_handle.h>
+
+
+extern "C" {
+#include "crypto_scrypt.h"
+}
+
+namespace android {
+
+static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) {
+
+ jbyte* data = (jbyte*)env->GetPrimitiveArrayCritical(handleArray, NULL);
+
+ if (data != NULL) {
+ const gatekeeper::password_handle_t *handle =
+ reinterpret_cast<const gatekeeper::password_handle_t *>(data);
+ jlong sid = handle->user_id;
+ env->ReleasePrimitiveArrayCritical(handleArray, data, JNI_ABORT);
+ return sid;
+ } else {
+ return 0;
+ }
+}
+
+static jbyteArray android_server_SyntheticPasswordManager_nativeScrypt(JNIEnv* env, jobject, jbyteArray password, jbyteArray salt, jint N, jint r, jint p, jint outLen) {
+ if (!password || !salt) {
+ return NULL;
+ }
+
+ int passwordLen = env->GetArrayLength(password);
+ int saltLen = env->GetArrayLength(salt);
+ jbyteArray ret = env->NewByteArray(outLen);
+
+ jbyte* passwordPtr = (jbyte*)env->GetByteArrayElements(password, NULL);
+ jbyte* saltPtr = (jbyte*)env->GetByteArrayElements(salt, NULL);
+ jbyte* retPtr = (jbyte*)env->GetByteArrayElements(ret, NULL);
+
+ int rc = crypto_scrypt((const uint8_t *)passwordPtr, passwordLen,
+ (const uint8_t *)saltPtr, saltLen, N, r, p, (uint8_t *)retPtr,
+ outLen);
+ env->ReleaseByteArrayElements(password, passwordPtr, JNI_ABORT);
+ env->ReleaseByteArrayElements(salt, saltPtr, JNI_ABORT);
+ env->ReleaseByteArrayElements(ret, retPtr, 0);
+
+ if (!rc) {
+ return ret;
+ } else {
+ SLOGE("scrypt failed");
+ return NULL;
+ }
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeSidFromPasswordHandle", "([B)J", (void*)android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle},
+ {"nativeScrypt", "([B[BIIII)[B", (void*)android_server_SyntheticPasswordManager_nativeScrypt},
+};
+
+int register_android_server_SyntheticPasswordManager(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/SyntheticPasswordManager",
+ sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 6f505d5..899640e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -45,6 +45,7 @@
int register_android_server_PersistentDataBlockService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
+int register_android_server_SyntheticPasswordManager(JNIEnv* env);
};
using namespace android;
@@ -85,6 +86,7 @@
register_android_server_Watchdog(env);
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
+ register_android_server_SyntheticPasswordManager(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 22dd9d7..dd44aa0 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.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -208,7 +209,7 @@
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String LOG_TAG = "DevicePolicyManager";
+ protected static final String LOG_TAG = "DevicePolicyManager";
private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
@@ -243,6 +244,8 @@
private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
+ private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -252,7 +255,6 @@
private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION
= "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
- private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002;
@@ -409,6 +411,7 @@
}
};
+ /** Listens only if mHasFeature == true. */
private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
@Override
@@ -420,6 +423,7 @@
}
};
+ /** Listens only if mHasFeature == true. */
private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
@Override
@@ -504,6 +508,8 @@
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
+ long mPasswordTokenHandle = 0;
+
public DevicePolicyData(int userHandle) {
mUserHandle = userHandle;
}
@@ -513,7 +519,21 @@
final Handler mHandler;
- BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ /** Listens on any device, even when mHasFeature == false. */
+ final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (StorageManager.inCryptKeeperBounce()) {
+ return;
+ }
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
+ .execute(userHandle);
+ }
+ };
+
+ /** Listens only if mHasFeature == true. */
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -559,14 +579,7 @@
}
});
}
- if (Intent.ACTION_USER_UNLOCKED.equals(action)
- || Intent.ACTION_USER_STARTED.equals(action)
- || KeyChain.ACTION_TRUST_STORE_CHANGED.equals(action)) {
- if (!StorageManager.inCryptKeeperBounce()) {
- new MonitoringCertNotificationTask().execute(
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL));
- }
- }
+
if (Intent.ACTION_USER_ADDED.equals(action)) {
sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
synchronized (DevicePolicyManagerService.this) {
@@ -1490,6 +1503,15 @@
mContext = context;
}
+ Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+ final String packageName = mContext.getPackageName();
+ return mContext.createPackageContextAsUser(packageName, 0, user);
+ }
+
+ Resources getResources() {
+ return mContext.getResources();
+ }
+
Owners newOwners() {
return new Owners(getUserManager(), getUserManagerInternal(),
getPackageManagerInternal());
@@ -1725,6 +1747,10 @@
boolean securityLogIsLoggingEnabled() {
return SecurityLog.isLoggingEnabled();
}
+
+ KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException {
+ return KeyChain.bindAsUser(mContext, user);
+ }
}
/**
@@ -1755,18 +1781,27 @@
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+ // 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.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+
if (!mHasFeature) {
// Skip the rest of the initialization
return;
}
- IntentFilter filter = new IntentFilter();
+
+ filter = new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2526,6 +2561,13 @@
out.endTag(null, TAG_INITIALIZATION_BUNDLE);
}
+ if (policy.mPasswordTokenHandle != 0) {
+ out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+ out.attribute(null, ATTR_VALUE,
+ Long.toString(policy.mPasswordTokenHandle));
+ out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -2732,6 +2774,9 @@
m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols"));
m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter"));
}
+ } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
+ policy.mPasswordTokenHandle = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2952,125 +2997,34 @@
}
}
- private class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
- @Override
- protected Void doInBackground(Integer... params) {
- int userHandle = params[0];
+ /**
+ * Remove deleted CA certificates from the "approved" list for a particular user, counting
+ * the number still remaining to approve.
+ *
+ * @param userHandle user to check for. This must be a real user and not, for example,
+ * {@link UserHandle#ALL}.
+ * @param installedCertificates the full set of certificate authorities currently installed for
+ * {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
+ * correspond to some subset of this.
+ *
+ * @return number of certificates yet to be approved by {@param userHandle}.
+ */
+ protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
+ final @NonNull Collection<String> installedCertificates) {
+ enforceManageUsers();
- if (userHandle == UserHandle.USER_ALL) {
- for (UserInfo userInfo : mUserManager.getUsers(true)) {
- manageNotification(userInfo.getUserHandle());
- }
- } else {
- manageNotification(UserHandle.of(userHandle));
- }
- return null;
- }
+ if (!mHasFeature) {
+ return installedCertificates.size();
+ } else {
+ final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
- private void manageNotification(UserHandle userHandle) {
- if (!mUserManager.isUserUnlocked(userHandle)) {
- return;
+ // Remove deleted certificates. Flush xml if necessary.
+ if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
+ saveSettingsLocked(userHandle.getIdentifier());
}
- // Call out to KeyChain to check for CAs which are waiting for approval.
- final List<String> pendingCertificates;
- try {
- pendingCertificates = getInstalledCaCertificates(userHandle);
- } catch (RemoteException | RuntimeException e) {
- Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
- return;
- }
-
- synchronized (DevicePolicyManagerService.this) {
- final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
-
- // Remove deleted certificates. Flush xml if necessary.
- if (policy.mAcceptedCaCertificates.retainAll(pendingCertificates)) {
- saveSettingsLocked(userHandle.getIdentifier());
- }
- // Trim to approved certificates.
- pendingCertificates.removeAll(policy.mAcceptedCaCertificates);
- }
-
- if (pendingCertificates.isEmpty()) {
- mInjector.getNotificationManager().cancelAsUser(
- null, MONITORING_CERT_NOTIFICATION_ID, userHandle);
- return;
- }
-
- // Build and show a warning notification
- int smallIconId;
- String contentText;
- int parentUserId = userHandle.getIdentifier();
- if (getProfileOwner(userHandle.getIdentifier()) != null) {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
- getProfileOwnerName(userHandle.getIdentifier()));
- smallIconId = R.drawable.stat_sys_certificate_info;
- parentUserId = getProfileParentId(userHandle.getIdentifier());
- } else if (getDeviceOwnerUserId() == userHandle.getIdentifier()) {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
- getDeviceOwnerName());
- smallIconId = R.drawable.stat_sys_certificate_info;
- } else {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_by_unknown);
- smallIconId = android.R.drawable.stat_sys_warning;
- }
-
- final int numberOfCertificates = pendingCertificates.size();
- Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
- dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- dialogIntent.setPackage("com.android.settings");
- dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, numberOfCertificates);
- dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
- PendingIntent notifyIntent = PendingIntent.getActivityAsUser(mContext, 0,
- dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
- new UserHandle(parentUserId));
-
- final Context userContext;
- try {
- final String packageName = mContext.getPackageName();
- userContext = mContext.createPackageContextAsUser(packageName, 0, userHandle);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
- return;
- }
- final Notification noti = new Notification.Builder(userContext)
- .setSmallIcon(smallIconId)
- .setContentTitle(mContext.getResources().getQuantityText(
- R.plurals.ssl_ca_cert_warning, numberOfCertificates))
- .setContentText(contentText)
- .setContentIntent(notifyIntent)
- .setPriority(Notification.PRIORITY_HIGH)
- .setShowWhen(false)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .build();
-
- mInjector.getNotificationManager().notifyAsUser(
- null, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
- }
-
- private List<String> getInstalledCaCertificates(UserHandle userHandle)
- throws RemoteException, RuntimeException {
- KeyChainConnection conn = null;
- try {
- conn = KeyChain.bindAsUser(mContext, userHandle);
- List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
- List<String> result = new ArrayList<>(aliases.size());
- for (int i = 0; i < aliases.size(); i++) {
- result.add(aliases.get(i).string);
- }
- return result;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return null;
- } catch (AssertionError e) {
- throw new RuntimeException(e);
- } finally {
- if (conn != null) {
- conn.close();
- }
- }
+ // Trim approved certificates from the count.
+ return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
}
}
@@ -4134,12 +4088,8 @@
mInjector.binderRestoreCallingIdentity(token);
}
}
-
@Override
public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
- if (!mHasFeature) {
- return false;
- }
final int callingUid = mInjector.binderGetCallingUid();
final int userHandle = mInjector.userHandleGetCallingUserId();
@@ -4150,15 +4100,18 @@
enforceNotManagedProfile(userHandle, "clear the active password");
}
- int quality;
synchronized (this) {
// If caller has PO (or DO) it can change the password, so see if that's the case first.
ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked(
null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid);
final boolean preN;
if (admin != null) {
- preN = getTargetSdk(admin.info.getPackageName(),
- userHandle) <= android.os.Build.VERSION_CODES.M;
+ final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle);
+ if (targetSdk >= Build.VERSION_CODES.O) {
+ throw new SecurityException("resetPassword() is deprecated for DPC targeting O"
+ + " or later");
+ }
+ preN = targetSdk <= android.os.Build.VERSION_CODES.M;
} else {
// Otherwise, make sure the caller has any active admin with the right policy.
admin = getActiveAdminForCallerLocked(null,
@@ -4209,7 +4162,15 @@
return false;
}
}
+ }
+ return resetPasswordInternal(password, 0, null, flags, callingUid, userHandle);
+ }
+
+ private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
+ int flags, int callingUid, int userHandle) {
+ int quality;
+ synchronized (this) {
quality = getPasswordQuality(null, userHandle, /* parent */ false);
if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -4299,11 +4260,20 @@
// Don't do this with the lock held, because it is going to call
// back in to the service.
final long ident = mInjector.binderClearCallingIdentity();
+ final boolean result;
try {
- if (!TextUtils.isEmpty(password)) {
- mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
+ if (token == null) {
+ if (!TextUtils.isEmpty(password)) {
+ mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
+ } else {
+ mLockPatternUtils.clearLock(null, userHandle);
+ }
+ result = true;
} else {
- mLockPatternUtils.clearLock(null, userHandle);
+ result = mLockPatternUtils.setLockCredentialWithToken(password,
+ TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE
+ : LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ tokenHandle, token, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -4320,8 +4290,7 @@
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
-
- return true;
+ return result;
}
private boolean isLockScreenSecureUnchecked(int userId) {
@@ -4635,7 +4604,7 @@
}
saveSettingsLocked(userId);
}
- new MonitoringCertNotificationTask().execute(userId);
+ new MonitoringCertNotificationTask(this, mInjector).execute(userId);
return true;
}
@@ -4659,7 +4628,7 @@
saveSettingsLocked(userInfo.id);
}
- new MonitoringCertNotificationTask().execute(userInfo.id);
+ new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
}
}
}
@@ -7154,7 +7123,7 @@
return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
}
- private int getProfileParentId(int userHandle) {
+ protected int getProfileParentId(int userHandle) {
final long ident = mInjector.binderClearCallingIdentity();
try {
UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -7271,6 +7240,7 @@
long id = mInjector.binderClearCallingIdentity();
try {
mIPackageManager.addPersistentPreferredActivity(filter, activity, userHandle);
+ mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
} catch (RemoteException re) {
// Shouldn't happen
} finally {
@@ -7289,6 +7259,7 @@
long id = mInjector.binderClearCallingIdentity();
try {
mIPackageManager.clearPackagePersistentPreferredActivities(packageName, userHandle);
+ mIPackageManager.flushPackageRestrictionsAsUser(userHandle);
} catch (RemoteException re) {
// Shouldn't happen
} finally {
@@ -9150,13 +9121,16 @@
}
final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
.getPackageName();
- final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid);
-
- for (String pkg : pkgs) {
- if (deviceOwnerPackageName.equals(pkg)) {
- return true;
+ try {
+ String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(callerUid);
+ for (String pkg : pkgs) {
+ if (deviceOwnerPackageName.equals(pkg)) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ return false;
}
- }
}
return false;
@@ -10102,7 +10076,9 @@
@Override
public boolean isDeviceProvisioned() {
- return !TextUtils.isEmpty(mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT));
+ synchronized (this) {
+ return getUserDataUnchecked(UserHandle.USER_SYSTEM).mUserSetupComplete;
+ }
}
private void removePackageIfRequired(final String packageName, final int userId) {
@@ -10677,4 +10653,98 @@
enforceDeviceOwnerOrManageUsers();
return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
}
+
+ @Override
+ public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
+ if (!mHasFeature) {
+ return false;
+ }
+ if (token == null || token.length < 32) {
+ throw new IllegalArgumentException("token must be at least 32-byte long");
+ }
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ DevicePolicyData policy = getUserData(userHandle);
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ if (policy.mPasswordTokenHandle != 0) {
+ mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, userHandle);
+ }
+
+ policy.mPasswordTokenHandle = mLockPatternUtils.addEscrowToken(token, userHandle);
+ saveSettingsLocked(userHandle);
+ return policy.mPasswordTokenHandle != 0;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public boolean clearResetPasswordToken(ComponentName admin) {
+ if (!mHasFeature) {
+ return false;
+ }
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mPasswordTokenHandle != 0) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ boolean result = mLockPatternUtils.removeEscrowToken(
+ policy.mPasswordTokenHandle, userHandle);
+ policy.mPasswordTokenHandle = 0;
+ saveSettingsLocked(userHandle);
+ return result;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isResetPasswordTokenActive(ComponentName admin) {
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mPasswordTokenHandle != 0) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle,
+ userHandle);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
+ int flags) {
+ Preconditions.checkNotNull(token);
+ synchronized (this) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mPasswordTokenHandle != 0) {
+ final String password = passwordOrNull != null ? passwordOrNull : "";
+ return resetPasswordInternal(password, policy.mPasswordTokenHandle, token,
+ flags, mInjector.binderGetCallingUid(), userHandle);
+ } else {
+ Slog.w(LOG_TAG, "No saved token handle");
+ }
+ }
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
new file mode 100644
index 0000000..03c137a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
@@ -0,0 +1,166 @@
+/*
+ * 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.devicepolicy;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
+ protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+ protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
+
+ private final DevicePolicyManagerService mService;
+ private final DevicePolicyManagerService.Injector mInjector;
+
+ public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
+ final DevicePolicyManagerService.Injector injector) {
+ super();
+ mService = service;
+ mInjector = injector;
+ }
+
+ @Override
+ protected Void doInBackground(Integer... params) {
+ int userHandle = params[0];
+
+ if (userHandle == UserHandle.USER_ALL) {
+ for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
+ repostOrClearNotification(userInfo.getUserHandle());
+ }
+ } else {
+ repostOrClearNotification(UserHandle.of(userHandle));
+ }
+ return null;
+ }
+
+ private void repostOrClearNotification(UserHandle userHandle) {
+ if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+ return;
+ }
+
+ // Call out to KeyChain to check for CAs which are waiting for approval.
+ final int pendingCertificateCount;
+ try {
+ pendingCertificateCount = mService.retainAcceptedCertificates(
+ userHandle, getInstalledCaCertificates(userHandle));
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+ return;
+ }
+
+ if (pendingCertificateCount != 0) {
+ showNotification(userHandle, pendingCertificateCount);
+ } else {
+ mInjector.getNotificationManager().cancelAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
+ }
+ }
+
+ private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
+ // Create a context for the target user.
+ final Context userContext;
+ try {
+ userContext = mInjector.createContextAsUser(userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+ return;
+ }
+
+ // Build and show a warning notification
+ int smallIconId;
+ String contentText;
+ int parentUserId = userHandle.getIdentifier();
+ Resources resources = mInjector.getResources();
+ if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getProfileOwnerName(userHandle.getIdentifier()));
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
+ } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getDeviceOwnerName());
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ } else {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
+ smallIconId = android.R.drawable.stat_sys_warning;
+ }
+
+ Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
+ dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ // TODO this next line is taken from original notification code in
+ // {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
+ dialogIntent.setPackage("com.android.settings");
+ dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
+ dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
+ PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
+ dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+ UserHandle.of(parentUserId));
+
+ final Notification noti = new Notification.Builder(userContext)
+ .setSmallIcon(smallIconId)
+ .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
+ pendingCertificateCount))
+ .setContentText(contentText)
+ .setContentIntent(notifyIntent)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setShowWhen(false)
+ .setColor(R.color.system_notification_accent_color)
+ .build();
+
+ mInjector.getNotificationManager().notifyAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
+ }
+
+ private List<String> getInstalledCaCertificates(UserHandle userHandle)
+ throws RemoteException, RuntimeException {
+ try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
+ List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
+ List<String> result = new ArrayList<>(aliases.size());
+ for (int i = 0; i < aliases.size(); i++) {
+ result.add(aliases.get(i).string);
+ }
+ return result;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ } catch (AssertionError e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 83e209d..31c8261 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -236,6 +237,8 @@
private static final String START_SENSOR_SERVICE = "StartSensorService";
private Future<?> mSensorServiceStart;
+ private Future<?> mZygotePreload;
+
/**
* Start the sensor service. This is a blocking call and can take time.
@@ -688,6 +691,26 @@
}
try {
+ final String SECONDARY_ZYGOTE_PRELOAD = "SecondaryZygotePreload";
+ // We start the preload ~1s before the webview factory preparation, to
+ // ensure that it completes before the 32 bit relro process is forked
+ // from the zygote. In the event that it takes too long, the webview
+ // RELRO process will block, but it will do so without holding any locks.
+ mZygotePreload = SystemServerInitThreadPool.get().submit(() -> {
+ try {
+ Slog.i(TAG, SECONDARY_ZYGOTE_PRELOAD);
+ BootTimingsTraceLog traceLog = new BootTimingsTraceLog(
+ SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+ traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
+ if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
+ Slog.e(TAG, "Unable to preload default resources");
+ }
+ traceLog.traceEnd();
+ } catch (Exception ex) {
+ Slog.e(TAG, "Exception preloading default resources", ex);
+ }
+ }, SECONDARY_ZYGOTE_PRELOAD);
+
traceBeginAndSlog("StartKeyAttestationApplicationIdProviderService");
ServiceManager.addService("sec_key_att_app_id_provider",
new KeyAttestationApplicationIdProviderService(context));
@@ -1615,6 +1638,8 @@
BootTimingsTraceLog traceLog = new BootTimingsTraceLog(
SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
traceLog.traceBegin(WEBVIEW_PREPARATION);
+ ConcurrentUtils.waitForFutureNoInterrupt(mZygotePreload, "Zygote preload");
+ mZygotePreload = null;
mWebViewUpdateService.prepareWebViewInSystemServer();
traceLog.traceEnd();
}, WEBVIEW_PREPARATION);
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9824c1d..9ac8295 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -19,20 +19,28 @@
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.app.PendingIntent;
+import android.Manifest;
import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.companion.ICompanionDeviceDiscoveryService;
+import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.ICompanionDeviceManager;
-import android.companion.ICompanionDeviceManagerService;
-import android.companion.IOnAssociateCallback;
+import android.companion.IFindDeviceCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.IBinder;
+import android.os.IDeviceIdleController;
import android.os.RemoteException;
-import android.util.Log;
+import android.os.ServiceManager;
+import android.util.Slog;
+import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
//TODO move to own package!
@@ -40,7 +48,8 @@
public class CompanionDeviceManagerService extends SystemService {
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
- "com.android.companiondevicemanager", ".DeviceDiscoveryService");
+ CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
+ ".DeviceDiscoveryService");
private static final boolean DEBUG = false;
private static final String LOG_TAG = "CompanionDeviceManagerService";
@@ -58,14 +67,13 @@
}
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
-
@Override
public void associate(
AssociationRequest request,
- IOnAssociateCallback callback,
- String callingPackage) throws RemoteException {
+ IFindDeviceCallback callback,
+ String callingPackage) {
if (DEBUG) {
- Log.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+ Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+ ", callingPackage = " + callingPackage + ")");
}
checkNotNull(request);
@@ -85,23 +93,24 @@
private ServiceConnection getServiceConnection(
final AssociationRequest<?> request,
- final IOnAssociateCallback callback,
+ final IFindDeviceCallback findDeviceCallback,
final String callingPackage) {
return new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
- Log.i(LOG_TAG,
+ Slog.i(LOG_TAG,
"onServiceConnected(name = " + name + ", service = "
+ service + ")");
}
try {
- ICompanionDeviceManagerService.Stub
+ ICompanionDeviceDiscoveryService.Stub
.asInterface(service)
.startDiscovery(
request,
- getCallback(callingPackage, callback),
- callingPackage);
+ callingPackage,
+ findDeviceCallback,
+ getServiceCallback());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -109,39 +118,50 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
+ if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
}
};
}
- private IOnAssociateCallback.Stub getCallback(
- String callingPackage,
- IOnAssociateCallback propagateTo) {
- return new IOnAssociateCallback.Stub() {
-
+ private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
+ return new ICompanionDeviceDiscoveryServiceCallback.Stub() {
@Override
- public void onSuccess(PendingIntent launcher)
- throws RemoteException {
- if (DEBUG) Log.i(LOG_TAG, "onSuccess(launcher = " + launcher + ")");
- recordSpecialPriviledgesForPackage(callingPackage);
- propagateTo.onSuccess(launcher);
- }
-
- @Override
- public void onFailure(CharSequence reason) throws RemoteException {
- if (DEBUG) Log.i(LOG_TAG, "onFailure()");
- propagateTo.onFailure(reason);
+ public void onDeviceSelected(String packageName, int userId) {
+ grantSpecialAccessPermissionsIfNeeded(packageName, userId);
}
};
}
- void recordSpecialPriviledgesForPackage(String priviledgedPackage) {
- //TODO Show dialog before recording notification access
-// final SettingStringHelper setting =
-// new SettingStringHelper(
-// getContext().getContentResolver(),
-// Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-// Binder.getCallingUid());
-// setting.write(ColonDelimitedSet.OfStrings.add(setting.read(), priviledgedPackage));
+ private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
+ packageName, PackageManager.GET_PERMISSIONS, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Error granting special access permissions to package:"
+ + packageName, e);
+ return;
+ }
+ try {
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.RUN_IN_BACKGROUND)) {
+ IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ try {
+ idleController.addPowerSaveWhitelistApp(packageName);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.USE_DATA_IN_BACKGROUND)) {
+ NetworkPolicyManager.from(getContext()).addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index db010b8..88f1a53 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -280,6 +280,21 @@
@Test
@UiThreadTest
+ public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
+ mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ generateNotificationRecord(null).getNotification(), new int[1], 0);
+ waitForIdle();
+ mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ generateNotificationRecord(null).getNotification(), new int[1], 0);
+ mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mContext.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
index c89d35c..c6265bc 100644
--- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -134,5 +134,13 @@
File storageDir = mStorage.mStorageDir;
assertTrue(FileUtils.deleteContents(storageDir));
}
+
+ protected static void assertArrayEquals(byte[] expected, byte[] actual) {
+ assertTrue(Arrays.equals(expected, actual));
+ }
+
+ protected static void assertArrayNotSame(byte[] expected, byte[] actual) {
+ assertFalse(Arrays.equals(expected, actual));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
index 613ec0b..cfdb5b1 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
@@ -21,9 +21,11 @@
import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
+import android.os.Process;
+import android.os.RemoteException;
import android.os.storage.IStorageManager;
import android.security.KeyStore;
-import android.service.gatekeeper.IGateKeeperService;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.android.internal.widget.LockPatternUtils;
@@ -38,16 +40,18 @@
private IActivityManager mActivityManager;
private LockPatternUtils mLockPatternUtils;
private IStorageManager mStorageManager;
+ private MockGateKeeperService mGatekeeper;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager, LockPatternUtils lockPatternUtils,
- IStorageManager storageManager) {
+ IStorageManager storageManager, MockGateKeeperService gatekeeper) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
mActivityManager = activityManager;
mLockPatternUtils = lockPatternUtils;
mStorageManager = storageManager;
+ mGatekeeper = gatekeeper;
}
@Override
@@ -89,13 +93,25 @@
public IStorageManager getStorageManager() {
return mStorageManager;
}
+
+ @Override
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return new MockSyntheticPasswordManager(storage, mGatekeeper);
+ }
+
+ @Override
+ public int binderGetCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+
}
protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
- LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore,
+ LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore,
IStorageManager storageManager, IActivityManager mActivityManager) {
super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
- storageManager));
+ storageManager, gatekeeper));
mGateKeeperService = gatekeeper;
}
@@ -105,12 +121,18 @@
}
@Override
- protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException {
+ protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException, KeyPermanentlyInvalidatedException {
byte[] storedData = mStorage.readChildProfileLock(userId);
if (storedData == null) {
throw new FileNotFoundException("Child profile lock file not found");
}
+ try {
+ if (mGateKeeperService.getSecureUserId(userId) == 0) {
+ throw new KeyPermanentlyInvalidatedException();
+ }
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
return new String(storedData);
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
index 4c2e171..ae9762a 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -123,6 +123,12 @@
UnifiedPassword, PRIMARY_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ //Clear unified challenge
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
+ PRIMARY_USER_ID);
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
}
public void testManagedProfileSeparateChallenge() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
index e81b02f..18da1a5 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
@@ -31,19 +31,36 @@
@Override
String getLockPatternFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getLockPatternFilename(userId)).getAbsolutePath();
}
@Override
String getLockPasswordFilename(int userId) {
- return new File(mStorageDir,
- super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getLockPasswordFilename(userId)).getAbsolutePath();
}
@Override
String getChildProfileLockFile(int userId) {
- return new File(mStorageDir,
- super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
+ return makeDirs(mStorageDir,
+ super.getChildProfileLockFile(userId)).getAbsolutePath();
+ }
+
+ @Override
+ protected File getSyntheticPasswordDirectoryForUser(int userId) {
+ return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
+ userId).getAbsolutePath());
+ }
+
+ private File makeDirs(File baseDir, String filePath) {
+ File path = new File(filePath);
+ if (path.getParent() == null) {
+ return new File(baseDir, filePath);
+ } else {
+ File mappedDir = new File(baseDir, path.getParent());
+ mappedDir.mkdirs();
+ return new File(mappedDir, path.getName());
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index d110fea..c68fbdc 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -329,6 +329,16 @@
assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
}
+ public void testSyntheticPasswordState() {
+ final byte[] data = {1,2,3,4};
+ mStorage.writeSyntheticPasswordState(10, 1234L, "state", data);
+ assertArrayEquals(data, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ assertEquals(null, mStorage.readSyntheticPasswordState(0, 1234L, "state"));
+
+ mStorage.deleteSyntheticPasswordState(10, 1234L, "state", true);
+ assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ }
+
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
index 15983ca..bc93341 100644
--- a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
+++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
@@ -149,6 +149,15 @@
return authTokenMap.get(uid);
}
+ public AuthToken getAuthTokenForSid(long sid) {
+ for(AuthToken token : authTokenMap.values()) {
+ if (token.sid == sid) {
+ return token;
+ }
+ }
+ return null;
+ }
+
public void clearAuthToken(int uid) {
authTokenMap.remove(uid);
}
diff --git a/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
new file mode 100644
index 0000000..93e3fc6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
+
+ private MockGateKeeperService mGateKeeper;
+
+ public MockSyntheticPasswordManager(LockSettingsStorage storage,
+ MockGateKeeperService gatekeeper) {
+ super(storage);
+ mGateKeeper = gatekeeper;
+ }
+
+ private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+
+ @Override
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
+ throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(blob.length);
+ buffer.put(blob, 0, blob.length);
+ buffer.flip();
+ int len;
+ len = buffer.getInt();
+ byte[] data = new byte[len];
+ buffer.get(data);
+ len = buffer.getInt();
+ byte[] appId = new byte[len];
+ buffer.get(appId);
+ long sid = buffer.getLong();
+ if (!Arrays.equals(appId, applicationId)) {
+ throw new AssertionFailedError("Invalid application id");
+ }
+ if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) {
+ throw new AssertionFailedError("No valid auth token");
+ }
+ return data;
+ }
+
+ @Override
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
+ + applicationId.length + Long.BYTES);
+ buffer.putInt(data.length);
+ buffer.put(data);
+ buffer.putInt(applicationId.length);
+ buffer.put(applicationId);
+ buffer.putLong(sid);
+ byte[] result = buffer.array();
+ mBlobs.put(blobKeyName, result);
+ return result;
+ }
+
+ @Override
+ protected void destroySPBlobKey(String keyAlias) {
+ }
+
+ @Override
+ protected long sidFromPasswordHandle(byte[] handle) {
+ return new MockGateKeeperService.VerifyHandle(handle).sid;
+ }
+
+ @Override
+ protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
+ try {
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, outLen * 8);
+ SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ return f.generateSecret(spec).getEncoded();
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 3a88e9c..c0b79be 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -117,6 +117,7 @@
private static final String SSID = "ssid";
private static final String SSID_2 = "ssid_2";
private static final String SSID_3 = "ssid_3";
+ private static final String INVALID_BSSID = "invalid_bssid";
private static final ComponentName RECOMMENDATION_SERVICE_COMP =
new ComponentName("newPackageName", "newScoringServiceClass");
private static final ScoredNetwork SCORED_NETWORK =
@@ -778,6 +779,54 @@
}
@Test
+ public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_nullSsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(null);
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_noneSsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE);
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_emptySsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn("");
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_invalidWifiInfo_invalidBssid() throws Exception {
+ when(mWifiInfo.getBSSID()).thenReturn(INVALID_BSSID);
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception {
NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
@@ -813,6 +862,24 @@
}
@Test
+ public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
+ List<ScanResult> invalidScanResults = Lists.newArrayList(
+ new ScanResult(),
+ createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
+ createScanResult(WifiSsid.NONE, SCORED_NETWORK.networkKey.wifiKey.bssid),
+ createScanResult(SSID, null),
+ createScanResult(SSID, INVALID_BSSID)
+ );
+ NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+ new NetworkScoreService.ScanResultsScoreCacheFilter(() -> invalidScanResults);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception {
NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults);
diff --git a/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
new file mode 100644
index 0000000..6e5ade1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
@@ -0,0 +1,329 @@
+/*
+ * 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;
+
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.SyntheticPasswordManager.AuthenticationToken;
+
+
+/**
+ * runtest frameworks-services -c com.android.server.SyntheticPasswordTests
+ */
+public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testPasswordBasedSyntheticPassword() throws RemoteException {
+ final int USER_ID = 10;
+ final String PASSWORD = "user-password";
+ final String BADPASSWORD = "bad-password";
+ MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+ AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
+ null, USER_ID);
+ long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID);
+
+ AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
+ assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
+
+ result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, BADPASSWORD, USER_ID);
+ assertNull(result.authToken);
+ }
+
+ private void disableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
+ }
+
+ private void enableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+ }
+
+ private boolean hasSyntheticPassword(int userId) throws RemoteException {
+ return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
+ }
+
+ public void testPasswordMigration() throws RemoteException {
+ final String PASSWORD = "testPasswordMigration-password";
+
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ // Performs migration
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+
+ // SP-based verification
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
+ enableSyntheticPassword(userId);
+ mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId);
+ }
+
+ public void testSyntheticPasswordChangeCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordChangeCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordChangeCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordVerifyCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordVerifyCredential-password";
+ final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+ public void testSyntheticPasswordClearCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // Untrusted change password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // Verify the password
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+
+ public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
+ final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testTokenBasedResetPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedClearPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String NEWPASSWORD = "password";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID);
+
+ mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(null, PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ // b/34600579
+ //TODO: add non-migration work profile case, and unify/un-unify transition.
+ //TODO: test token after user resets password
+ //TODO: test token based reset after unified work challenge
+ //TODO: test clear password after unified work challenge
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 3b92a34..e6dd13f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -29,6 +29,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Pair;
@@ -375,5 +376,10 @@
boolean isBuildDebuggable() {
return context.buildMock.isDebuggable;
}
+
+ @Override
+ KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
+ return context.keyChainConnection;
+ }
}
}
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 6fb65d5..23a1bb4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,8 @@
import android.Manifest.permission;
import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -32,6 +34,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.IIpConnectivityMetrics;
@@ -52,10 +55,14 @@
import android.util.Pair;
import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserRestrictionsUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -76,13 +83,16 @@
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -1193,6 +1203,53 @@
return uid;
}
+ public void testCertificateDisclosure() throws Exception {
+ final int userId = DpmMockContext.CALLER_USER_HANDLE;
+ final UserHandle user = UserHandle.of(userId);
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.callerPermissions.add(permission.MANAGE_USERS);
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ ParceledListSlice<ParcelableString> oneCert = asSlice(new String[] {"1"});
+ ParceledListSlice<ParcelableString> fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
+
+ final String TEST_STRING = "Test for exactly 2 certs out of 4";
+ doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
+
+ // Given that we have exactly one certificate installed,
+ when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
+ // when that certificate is approved,
+ dpms.approveCaCert(oneCert.getList().get(0).string, userId, true);
+ // a notification should not be shown.
+ verify(mContext.notificationManager, timeout(1000))
+ .cancelAsUser(anyString(), anyInt(), eq(user));
+
+ // Given that we have four certificates installed,
+ when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
+ // when two of them are approved (one of them approved twice hence no action),
+ dpms.approveCaCert(fourCerts.getList().get(0).string, userId, true);
+ dpms.approveCaCert(fourCerts.getList().get(1).string, userId, true);
+ // a notification should be shown saying that there are two certificates left to approve.
+ verify(mContext.notificationManager, timeout(1000))
+ .notifyAsUser(anyString(), anyInt(), argThat(
+ new BaseMatcher<Notification>() {
+ @Override
+ public boolean matches(Object item) {
+ final Notification noti = (Notification) item;
+ return TEST_STRING.equals(
+ noti.extras.getString(Notification.EXTRA_TITLE));
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "Notification{title=\"" + TEST_STRING + "\"}");
+ }
+ }), eq(user));
+ }
+
/**
* Simple test for delegate set/get and general delegation. Tests verifying that delegated
* privileges can acually be exercised by a delegate are not covered here.
@@ -3679,6 +3736,41 @@
dpm.getPermissionGrantState(admin1, app2, permission));
}
+ public void testResetPasswordWithToken() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ // test token validation
+ try {
+ dpm.setResetPasswordToken(admin1, new byte[31]);
+ fail("should not have accepted tokens too short");
+ } catch (IllegalArgumentException expected) {
+ }
+ // test adding a token
+ final byte[] token = new byte[32];
+ final long handle = 123456;
+ final String password = "password";
+ when(mContext.lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(handle);
+ assertTrue(dpm.setResetPasswordToken(admin1, token));
+
+ // test password activation
+ when(mContext.lockPatternUtils.isEscrowTokenActive(eq(handle), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+ assertTrue(dpm.isResetPasswordTokenActive(admin1));
+
+ // test reset password with token
+ when(mContext.lockPatternUtils.setLockCredentialWithToken(eq(password),
+ eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), eq(handle), eq(token),
+ eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+ assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
+
+ // test removing a token
+ when(mContext.lockPatternUtils.removeEscrowToken(eq(handle), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+ assertTrue(dpm.clearResetPasswordToken(admin1));
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
@@ -3734,4 +3826,20 @@
assertTrue(dpm.setProfileOwner(admin, null, userId));
mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
}
+
+ /**
+ * Convert String[] to ParceledListSlice<ParcelableString>.
+ * <p>
+ * TODO: This shouldn't be necessary. If ParcelableString does need to exist, it also needs
+ * a real constructor.
+ */
+ private static ParceledListSlice<ParcelableString> asSlice(String[] s) {
+ List<ParcelableString> list = new ArrayList<>(s.length);
+ for (int i = 0; i < s.length; i++) {
+ ParcelableString item = new ParcelableString();
+ item.string = s[i];
+ list.add(i, item);
+ }
+ return new ParceledListSlice<ParcelableString>(list);
+ }
}
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 22cd135..46aaf83 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -43,9 +43,11 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
+import android.util.ArrayMap;
import android.view.IWindowManager;
import com.android.internal.widget.LockPatternUtils;
@@ -58,10 +60,12 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -290,6 +294,7 @@
public final TelephonyManager telephonyManager;
public final AccountManager accountManager;
public final AlarmManager alarmManager;
+ public final KeyChain.KeyChainConnection keyChainConnection;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
@@ -300,6 +305,9 @@
public final BuildMock buildMock = new BuildMock();
+ /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
+ public final Map<UserHandle, Context> userContexts = new ArrayMap<>();
+
public String packageName = null;
public ApplicationInfo applicationInfo = null;
@@ -335,6 +343,7 @@
telephonyManager = mock(TelephonyManager.class);
accountManager = mock(AccountManager.class);
alarmManager = mock(AlarmManager.class);
+ keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(context.getPackageManager());
@@ -690,6 +699,19 @@
}
@Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ if (!userContexts.containsKey(user)) {
+ return super.createPackageContextAsUser(packageName, flags, user);
+ }
+ if (!getPackageName().equals(packageName)) {
+ throw new UnsupportedOperationException(
+ "Creating a context as another package is not implemented");
+ }
+ return userContexts.get(user);
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return contentResolver;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index ed6779c..43e2610 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -174,6 +174,8 @@
anyInt(),
eq(UserHandle.getUserId(packageUid)));
+ doReturn(new String[] {admin.getPackageName()}).when(mMockContext.ipackageManager)
+ .getPackagesForUid(eq(packageUid));
// Set up getPackageInfo().
markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid));
}
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 980aa2d..0c53167 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -75,7 +75,9 @@
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.MaskableIconDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -244,6 +246,8 @@
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(
+ getTestContext().getResources(), R.drawable.icon2));
final ShortcutInfo si1 = makeShortcut(
"shortcut1",
@@ -261,12 +265,18 @@
icon2,
makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
/* weight */ 12);
- final ShortcutInfo si3 = makeShortcut("shortcut3");
+ final ShortcutInfo si3 = makeShortcut(
+ "shortcut3",
+ "Title 3",
+ /* activity */ null,
+ icon3,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 13);
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+ assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
assertShortcutIds(assertAllNotKeyFieldsOnly(
mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2");
+ "shortcut1", "shortcut2", "shortcut3");
assertEquals(2, mManager.getRemainingCallCount());
// TODO: Check fields
@@ -550,7 +560,7 @@
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
- final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ final Icon bmp64x64_maskable = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_64x64));
final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_512x512));
@@ -561,7 +571,7 @@
makeShortcutWithIcon("res32x32", res32x32),
makeShortcutWithIcon("res64x64", res64x64),
makeShortcutWithIcon("bmp32x32", bmp32x32),
- makeShortcutWithIcon("bmp64x64", bmp64x64),
+ makeShortcutWithIcon("bmp64x64", bmp64x64_maskable),
makeShortcutWithIcon("bmp512x512", bmp512x512),
makeShortcut("none")
)));
@@ -691,6 +701,15 @@
bmp = pfdToBitmap(
mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0));
assertBitmapSize(128, 128, bmp);
+
+ Drawable dr = mLauncherApps.getShortcutIconDrawable(
+ makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
+ assertTrue(dr instanceof MaskableIconDrawable);
+ float viewportPercentage = 1 / (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage());
+ assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
+ dr.getIntrinsicWidth());
+ assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
+ dr.getIntrinsicHeight());
}
public void testCleanupDanglingBitmaps() throws Exception {
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 562de414..28ec4fd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -932,6 +932,74 @@
dumpUserFile(USER_10);
}
+ public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
+ mRunningUsers.put(USER_10, true);
+
+ setCaller(CALLING_PACKAGE_1, USER_10);
+
+ final Icon bmp32x32 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(bmp32x32)
+ .setTitle("title")
+ .setText("text")
+ .setDisabledMessage("dismes")
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(123)
+ .setExtras(pb)
+ .build();
+ sorig.setTimestamp(mInjectedCurrentTimeMillis);
+
+ mManager.addDynamicShortcuts(list(sorig));
+
+ mInjectedCurrentTimeMillis += 1;
+ final long now = mInjectedCurrentTimeMillis;
+ mInjectedCurrentTimeMillis += 1;
+
+ dumpsysOnLogcat("before save");
+
+ // Save and load.
+ mService.saveDirtyInfo();
+ initService();
+ mService.handleUnlockUser(USER_10);
+
+ dumpUserFile(USER_10);
+ dumpsysOnLogcat("after load");
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
+
+ assertEquals(USER_10, si.getUserId());
+ assertEquals(HANDLE_USER_10, si.getUserHandle());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
+ assertEquals("id", si.getId());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(0, si.getRank());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
+ | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_MASKABLE_BITMAP,
+ si.getFlags());
+ assertNotNull(si.getBitmapPath()); // Something should be set.
+ assertEquals(0, si.getIconResourceId());
+ assertTrue(si.getLastChangedTimestamp() < now);
+
+ dumpUserFile(USER_10);
+ }
+
public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
mRunningUsers.put(USER_10, true);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 96070b8..7964cf2 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -317,6 +317,15 @@
public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
/**
+ * The number of milliseconds that Telecom should wait after disconnecting a call via the
+ * ACTION_NEW_OUTGOING_CALL broadcast, in order to wait for the app which cancelled the call
+ * to make a new one.
+ * @hide
+ */
+ public static final String EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT =
+ "android.telecom.extra.NEW_OUTGOING_CALL_CANCEL_TIMEOUT";
+
+ /**
* A boolean meta-data value indicating whether an {@link InCallService} implements an
* in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
* would also like to replace the in-call interface should set this meta-data to {@code true} in
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 0dcd0f1..d51d75e 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -24,6 +24,7 @@
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
@@ -374,6 +375,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
throw new UnsupportedOperationException();
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 0aa20ef..8a5fd4b6 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -15,43 +15,43 @@
*/
package com.android.tests.applaunch;
-import java.io.OutputStreamWriter;
-
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActivityManager;
import android.app.ActivityManager.ProcessErrorStateInfo;
+import android.app.IActivityManager;
+import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.app.UiAutomation;
-import android.app.IActivityManager;
import android.support.test.rule.logging.AtraceLogger;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;
import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
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.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
-import android.os.ParcelFileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.InputStreamReader;
-
/**
* This test is intended to measure the time it takes for the apps to start.
* Names of the applications are passed in command line, and the
@@ -73,6 +73,7 @@
private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
private static final String KEY_LAUNCH_ORDER = "launch_order";
private static final String KEY_DROP_CACHE = "drop_cache";
+ private static final String KEY_SIMULATE_MAINTANANCE = "simulate_maintanance";
private static final String KEY_SIMPLEPPERF_CMD = "simpleperf_cmd";
private static final String KEY_TRACE_ITERATIONS = "trace_iterations";
private static final String KEY_LAUNCH_DIRECTORY = "launch_directory";
@@ -97,6 +98,7 @@
private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
private static final String APP_LAUNCH_CMD = "am start -W -n";
private static final String SUCCESS_MESSAGE = "Status: ok";
+ private static final String PROFILE_COMPILE_SUCCESS = "Success";
private static final String THIS_TIME = "ThisTime:";
private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d";
private static final String TRACE_ITERATION = "TRACE_ITERATION - %d";
@@ -104,6 +106,8 @@
private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION";
private static final String LAUNCH_ORDER_CYCLIC = "cyclic";
private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential";
+ private static final String SPEED_PROFILE_CMD = "cmd package compile -f -m speed-profile %s";
+
private Map<String, Intent> mNameToIntent;
@@ -124,6 +128,7 @@
private File mFile = null;
private FileOutputStream mOutputStream = null;
private BufferedWriter mBufferedWriter = null;
+ private boolean mSimulateMaintanance = false;
@Override
@@ -149,6 +154,8 @@
mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE));
mSimplePerfCmd = args.getString(KEY_SIMPLEPPERF_CMD);
mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC);
+ mSimulateMaintanance = Boolean.parseBoolean(args.getString(KEY_SIMULATE_MAINTANANCE));
+
createMappings();
parseArgs(args);
checkAccountSignIn();
@@ -229,6 +236,12 @@
sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
closeApp(launch.getApp(), true);
dropCache();
+ if (mSimulateMaintanance) {
+ String appPkgName = mNameToIntent.get(launch.getApp())
+ .getComponent().getPackageName();
+ assertTrue(String.format("Not able to speed profile the app : %s",
+ appPkgName), profileCompileApp(appPkgName));
+ }
sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
}
@@ -305,6 +318,26 @@
}
/**
+ * Compile the app package using speed compile command and return true or false
+ * based on status of the compilation command.
+ */
+ private boolean profileCompileApp(String appPkgName) throws IOException {
+ Log.i(TAG, "Starting to speed profile " + appPkgName);
+ try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation().
+ executeShellCommand(String.format(SPEED_PROFILE_CMD, appPkgName));
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
+ new FileInputStream(result.getFileDescriptor())))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ if (line.contains(PROFILE_COMPILE_SUCCESS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
* If launch order is "cyclic" then apps will be launched one after the
* other for each iteration count.
* If launch order is "sequential" then each app will be launched for given number
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 0fa4545..32e1b96 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -210,10 +210,9 @@
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
- // TODO: Verify proper cleanup is performed:
- // inOrder.verify(mStatsService).forceUpdate();
- // inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- // inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mStatsService).forceUpdate();
+ inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
@@ -230,10 +229,9 @@
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- // TODO: Verify proper cleanup is performed:
- // inOrder.verify(mStatsService).forceUpdate();
- // inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- // inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mStatsService).forceUpdate();
+ inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
@@ -296,6 +294,18 @@
IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
}
+ @Test
+ public void ignoresDuplicateUpstreamNotifications() throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+
+ for (int i = 0; i < 5; i++) {
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+ }
+
/**
* Send a command to the state machine under test, and run the event loop to idle.
*
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 35cf903..9bc8e18 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -31,6 +31,7 @@
import android.annotation.Nullable;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -697,6 +698,22 @@
/**
+ * Retrieve the Typeface for the attribute at <var>index</var>.
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Typeface for the attribute, or null if not defined.
+ */
+ @Override
+ public Typeface getFont(int index) {
+ if (!hasValue(index)) {
+ return null;
+ }
+
+ ResourceValue value = mResourceData[index];
+ return ResourceHelper.getFont(value, mContext, mTheme);
+ }
+
+ /**
* Retrieve the CharSequence[] for the attribute at <var>index</var>.
* This gets the resource ID of the selected attribute, and uses
* {@link Resources#getTextArray Resources.getTextArray} of the owning
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index e0f8e1c..6e3a8e8 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -43,12 +43,14 @@
import android.annotation.Nullable;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.icu.text.PluralRules;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LruCache;
import android.util.TypedValue;
+import android.view.DisplayAdjustments;
import android.view.ViewGroup.LayoutParams;
import java.io.File;
@@ -70,7 +72,8 @@
DisplayMetrics metrics,
Configuration config,
LayoutlibCallback layoutlibCallback) {
- Resources resources = new Resources(assets, metrics, config);
+ Resources resources = new Resources(Resources_Delegate.class.getClassLoader());
+ resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()));
resources.mContext = context;
resources.mLayoutlibCallback = layoutlibCallback;
return Resources.mSystem = resources;
@@ -779,6 +782,35 @@
}
@LayoutlibDelegate
+ static Typeface getFont(Resources resources, int id) throws
+ NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ if (value != null) {
+ return ResourceHelper.getFont(value.getSecond(), resources.mContext, null);
+ }
+
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
+ static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
+ NotFoundException {
+ Resources_Delegate.getValue(resources, id, outValue, true);
+ if (outValue.string != null) {
+ return ResourceHelper.getFont(outValue.string.toString(), resources.mContext, null,
+ mPlatformResourceFlag[0]);
+ }
+
+ throwException(resources, id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @LayoutlibDelegate
static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index a43e545..fb24c01 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -344,7 +344,9 @@
ffd.addFont(fontInfo);
return true;
}
- fontStream = assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING);
+ fontStream = isAsset ?
+ assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING) :
+ assetRepository.openNonAsset(cookie, path, AssetManager.ACCESS_STREAMING);
if (fontStream == null) {
Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
path);
diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
similarity index 65%
copy from core/java/android/companion/ICompanionDeviceManagerService.aidl
copy to tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
index ff2a7eb..ce669cb 100644
--- a/core/java/android/companion/ICompanionDeviceManagerService.aidl
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package android.companion;
+package android.graphics;
-import android.companion.AssociationRequest;
-import android.companion.IOnAssociateCallback;
+import android.annotation.NonNull;
-
-/** @hide */
-interface ICompanionDeviceManagerService {
- void startDiscovery(
- in AssociationRequest request,
- in IOnAssociateCallback callback,
- in String callingPackage);
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Typeface_Accessor {
+ public static boolean isSystemFont(@NonNull String fontName) {
+ return Typeface.sSystemFontMap.containsKey(fontName);
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index eee7473..174bbcf 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -863,29 +863,6 @@
}
mLocalMatrix.set(copy.mLocalMatrix);
-
- final ArrayList<Object> children = copy.mChildren;
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < children.size(); i++) {
- Object copyChild = children.get(i);
- if (copyChild instanceof VGroup_Delegate) {
- VGroup_Delegate copyGroup = (VGroup_Delegate) copyChild;
- mChildren.add(new VGroup_Delegate(copyGroup, targetsMap));
- } else {
- VPath_Delegate newPath;
- if (copyChild instanceof VFullPath_Delegate) {
- newPath = new VFullPath_Delegate((VFullPath_Delegate) copyChild);
- } else if (copyChild instanceof VClipPath_Delegate) {
- newPath = new VClipPath_Delegate((VClipPath_Delegate) copyChild);
- } else {
- throw new IllegalStateException("Unknown object in the tree!");
- }
- mChildren.add(newPath);
- if (newPath.mPathName != null) {
- targetsMap.put(newPath.mPathName, newPath);
- }
- }
- }
}
private VGroup_Delegate() {
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index b19cb58..b6e6ec0 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -38,7 +38,10 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.widget.ImageView;
import android.widget.NumberPicker;
import java.io.File;
@@ -401,6 +404,15 @@
numberPicker.setMaxValue(Integer.parseInt(maxValue));
}
}
+ else if (view instanceof ImageView) {
+ ImageView img = (ImageView) view;
+ Drawable drawable = img.getDrawable();
+ if (drawable instanceof Animatable) {
+ if (!((Animatable) drawable).isRunning()) {
+ ((Animatable) drawable).start();
+ }
+ }
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index 381eb1f..494ffa1 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -18,6 +18,7 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference;
@@ -54,8 +55,8 @@
public static void doFrame(long frameTimeNanos) {
Choreographer thisChoreographer = Choreographer.getInstance();
- thisChoreographer.mLastFrameTimeNanos = frameTimeNanos;
-
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer
+ .getFrameIntervalNanos();
thisChoreographer.mFrameInfo.markInputHandlingStart();
thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index 77b131f..4ffb2e2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -132,7 +132,7 @@
@Override
public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
IAnimationListener listener) {
- if (parentView instanceof ViewGroup == false) {
+ if (!(parentView instanceof ViewGroup)) {
throw new IllegalArgumentException("parentView is not a ViewGroup");
}
@@ -155,10 +155,10 @@
@Override
public Result moveChild(Object parentView, Object childView, int index,
Map<String, String> layoutParams, IAnimationListener listener) {
- if (parentView instanceof ViewGroup == false) {
+ if (!(parentView instanceof ViewGroup)) {
throw new IllegalArgumentException("parentView is not a ViewGroup");
}
- if (childView instanceof View == false) {
+ if (!(childView instanceof View)) {
throw new IllegalArgumentException("childView is not a View");
}
@@ -179,7 +179,7 @@
@Override
public Result removeChild(Object childView, IAnimationListener listener) {
- if (childView instanceof View == false) {
+ if (!(childView instanceof View)) {
throw new IllegalArgumentException("childView is not a View");
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index d392f21..91668af 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -100,7 +100,7 @@
mView.setText(text);
}
- public void setGravity(int gravity) {
+ private void setGravity(int gravity) {
mView.setGravity(gravity);
}
}
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 00799a1..0039476 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
@@ -24,6 +24,7 @@
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
@@ -859,6 +860,11 @@
}
@Override
+ public ChangedPackages getChangedPackages(int sequenceNumber) {
+ return null;
+ }
+
+ @Override
public boolean isUpgrade() {
return false;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index 2fe3ed5..287334c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -305,6 +305,7 @@
return Bridge.getResourceId(ResourceType.ID, ID_PREFIX + name);
}
+ @SuppressWarnings("deprecation")
@Override
public void requestFitSystemWindows() {
// The framework call would usually bubble up to ViewRootImpl but, in layoutlib, Layout will
@@ -416,6 +417,7 @@
}
}
+ @SuppressWarnings("SameParameterValue")
private int getDimension(String attr, boolean isFramework, int defaultValue) {
ResourceValue value = mResources.findItemInTheme(attr, isFramework);
value = mResources.resolveResValue(value);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 85fe2a4..d21955e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -144,7 +144,7 @@
private static final class PostInflateException extends Exception {
private static final long serialVersionUID = 1L;
- public PostInflateException(String message) {
+ private PostInflateException(String message) {
super(message);
}
}
@@ -242,11 +242,13 @@
// Then measure only the content with UNSPECIFIED to see the size difference
// and apply this to the screen size.
+ View measuredView = mContentRoot.getChildAt(0);
+
// first measure the full layout, with EXACTLY to get the size of the
// content as it is inside the decor/dialog
@SuppressWarnings("deprecation")
Pair<Integer, Integer> exactMeasure = measureView(
- mViewRoot, mContentRoot.getChildAt(0),
+ mViewRoot, measuredView,
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
@@ -258,6 +260,10 @@
mMeasuredScreenWidth, widthMeasureSpecMode,
mMeasuredScreenHeight, heightMeasureSpecMode);
+ // If measuredView is not null, exactMeasure nor result will be null.
+ assert exactMeasure != null;
+ assert result != null;
+
// now look at the difference and add what is needed.
if (renderingMode.isHorizExpand()) {
int measuredWidth = exactMeasure.getFirst();
@@ -406,8 +412,7 @@
* @param canvas an optional canvas to render the views to. If null, only the measure and
* layout steps will be executed.
*/
- private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
- @Nullable Canvas canvas, int width, int height) {
+ private static Result renderAndBuildResult(@NonNull ViewGroup viewRoot, @Nullable Canvas canvas) {
if (canvas == null) {
return SUCCESS.createResult();
}
@@ -551,7 +556,7 @@
long initialTime = System_Delegate.nanoTime();
if (!mFirstFrameExecuted) {
// We need to run an initial draw call to initialize the animations
- renderAndBuildResult(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ renderAndBuildResult(mViewRoot, NOP_CANVAS);
// The first frame will initialize the animations
Choreographer_Delegate.doFrame(initialTime);
@@ -560,8 +565,7 @@
// Second frame will move the animations
Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
}
- renderResult = renderAndBuildResult(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth,
- mMeasuredScreenHeight);
+ renderResult = renderAndBuildResult(mViewRoot, mCanvas);
}
mSystemViewInfoList =
@@ -1206,7 +1210,7 @@
* Sets up a {@link TabHost} object.
* @param tabHost the TabHost to setup.
* @param layoutlibCallback The project callback object to access the project R class.
- * @throws PostInflateException
+ * @throws PostInflateException if TabHost is missing the required ids for TabHost
*/
private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
throws PostInflateException {
@@ -1254,12 +1258,7 @@
TabSpec spec = tabHost.newTabSpec("tag")
.setIndicator("Tab Label", tabHost.getResources()
.getDrawable(android.R.drawable.ic_menu_info_details, null))
- .setContent(new TabHost.TabContentFactory() {
- @Override
- public View createTabContent(String tag) {
- return new LinearLayout(getContext());
- }
- });
+ .setContent(tag -> new LinearLayout(getContext()));
tabHost.addTab(spec);
} else {
// for each child of the frameLayout, add a new TabSpec
@@ -1333,8 +1332,8 @@
int childCount = viewGroup.getChildCount();
if (viewGroup == mContentRoot) {
- List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
- List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
+ List<ViewInfo> childrenWithoutOffset = new ArrayList<>(childCount);
+ List<ViewInfo> childrenWithOffset = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
ViewInfo[] childViewInfo =
visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
@@ -1345,7 +1344,7 @@
mViewInfoList = childrenWithOffset;
return childrenWithoutOffset;
} else {
- List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
+ List<ViewInfo> children = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
isContentFrame));
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index c197e40..b3a2d3e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -38,16 +38,20 @@
import android.content.res.ColorStateList;
import android.content.res.ComplexColor;
import android.content.res.ComplexColor_Accessor;
+import android.content.res.FontResourcesParser;
import android.content.res.GradientColor;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.NinePatch_Delegate;
import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Accessor;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
+import android.text.FontConfig;
import android.util.TypedValue;
import java.io.File;
@@ -367,6 +371,89 @@
return null;
}
+ /**
+ * Returns a {@link Typeface} given a font name. The font name, can be a system font family
+ * (like sans-serif) or a full path if the font is to be loaded from resources.
+ */
+ public static Typeface getFont(String fontName, BridgeContext context, Theme theme, boolean
+ isFramework) {
+ if (fontName == null) {
+ return null;
+ }
+
+ if (Typeface_Accessor.isSystemFont(fontName)) {
+ // Shortcut for the case where we are asking for a system font name. Those are not
+ // loaded using external resources.
+ return null;
+ }
+
+ // Check if this is an asset that we've already loaded dynamically
+ Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
+ if (typeface != null) {
+ return typeface;
+ }
+
+ String lowerCaseValue = fontName.toLowerCase();
+ if (lowerCaseValue.endsWith(".xml")) {
+ // create a block parser for the file
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ XmlPullParser parser = null;
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(fontName);
+ }
+ else {
+ File f = new File(fontName);
+ if (f.isFile()) {
+ try {
+ parser = ParserFactory.create(f);
+ } catch (XmlPullParserException | FileNotFoundException e) {
+ // this is an error and not warning since the file existence is checked before
+ // attempting to parse it.
+ Bridge.getLog().error(null, "Failed to parse file " + fontName,
+ e, null /*data*/);
+ }
+ }
+ }
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ parser, context, isFramework);
+ try {
+ FontConfig config = FontResourcesParser.parse(blockParser, context
+ .getResources());
+ typeface = Typeface.createFromResources(config, context.getAssets(),
+ fontName);
+ } catch (XmlPullParserException | IOException e) {
+ Bridge.getLog().error(null, "Failed to parse file " + fontName,
+ e, null /*data*/);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s does not exist (or is not a file)", fontName),
+ null /*data*/);
+ }
+ } else {
+ typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
+ }
+
+ return typeface;
+ }
+
+ /**
+ * Returns a {@link Typeface} given a font name. The font name, can be a system font family
+ * (like sans-serif) or a full path if the font is to be loaded from resources.
+ */
+ public static Typeface getFont(ResourceValue value, BridgeContext context, Theme theme) {
+ if (value == null) {
+ return null;
+ }
+
+ return getFont(value.getValue(), context, theme, value.isFramework());
+ }
+
private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
// see if we still have both the chunk and the bitmap in the caches
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
index 96b795a..f149b6c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
@@ -34,12 +34,9 @@
@Override
public boolean markSupported() {
- if (mFakeMarkSupport) {
- // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
- return true;
- }
+ // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
+ return mFakeMarkSupport || super.markSupported();
- return super.markSupported();
}
public void disableFakeMarkSupport() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index 040191e..b89718f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -52,9 +52,7 @@
Exception ex;
try {
return method.invoke(object, args);
- } catch (IllegalAccessException e) {
- ex = e;
- } catch (InvocationTargetException e) {
+ } catch (IllegalAccessException | InvocationTargetException e) {
ex = e;
}
throw new ReflectionException(ex);
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
new file mode 100644
index 0000000..b2baa98
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
new file mode 100644
index 0000000..b1e9206
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/testfont" />
+ <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/testfont2" />
+</font-family>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf
new file mode 100644
index 0000000..2852302
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf
new file mode 100644
index 0000000..b7bf5b4
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
new file mode 100644
index 0000000..c63b211
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="CONDENSED"
+ android:textSize="50sp"
+ android:fontFamily="sans-serif-condensed"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="CONDENSED ITALIC"
+ android:textSize="30sp"
+ android:fontFamily="sans-serif-condensed"
+ android:textStyle="italic"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="MONOSPACE"
+ android:textSize="50sp"
+ android:fontFamily="monospace"/>
+
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_height="30dp" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Custom"
+ android:textSize="20sp"
+ android:fontFamily="@font/testfont"/>
+
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_height="30dp" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Custom family"
+ android:textSize="20sp"
+ android:fontFamily="@font/testfamily"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Custom family"
+ android:textSize="20sp"
+ android:fontFamily="@font/testfamily"
+ android:textStyle="italic"/>
+
+</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 ded52a7..b15ee95 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
@@ -25,13 +25,15 @@
import org.junit.runners.Suite.SuiteClasses;
import android.graphics.Matrix_DelegateTest;
+import android.util.BridgeXmlPullAttributesTest;
/**
* Suite used by the layoutlib build system
*/
@RunWith(Suite.class)
@SuiteClasses({
- RenderTests.class, LayoutParserWrapperTest.class, BridgeXmlBlockParserTest.class,
+ RenderTests.class, LayoutParserWrapperTest.class,
+ BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
Matrix_DelegateTest.class, TestDelegates.class
})
public class Main {
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 3e5f9e0..67b42a7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -35,6 +35,7 @@
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.layoutlib.bridge.intensive.util.ImageUtils;
import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
import com.android.layoutlib.bridge.intensive.util.TestUtils;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.utils.ILogger;
@@ -537,6 +538,7 @@
configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
targetSdk, getLayoutLog());
sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
+ sessionParams.setAssetRepository(new TestAssetRepository());
return sessionParams;
}
}
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 73e51ec..913519c 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
@@ -384,4 +384,10 @@
strings);
assertTrue(sRenderMessages.isEmpty());
}
+
+ @Test
+ public void testFonts() throws ClassNotFoundException {
+ // TODO: styles seem to be broken in TextView
+ renderAndVerify("fonts_test.xml", "font_test.png");
+ }
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
index bae2dc0c..8ebfc65 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
@@ -132,7 +132,11 @@
@Override
public Integer getResourceId(ResourceType type, String name) {
- return mResources.get(type).get(name);
+ Map<String, Integer> resName2Id = mResources.get(type);
+ if (resName2Id == null) {
+ return null;
+ }
+ return resName2Id.get(name);
}
@Override
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
new file mode 100644
index 0000000..0856ac9
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
@@ -0,0 +1,54 @@
+/*
+ * 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.layoutlib.bridge.intensive.util;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link AssetRepository} used for render tests.
+ */
+public class TestAssetRepository extends AssetRepository {
+ private static InputStream open(String path) throws FileNotFoundException {
+ File asset = new File(path);
+ if (asset.isFile()) {
+ return new FileInputStream(asset);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public InputStream openAsset(String path, int mode) throws IOException {
+ return open(path);
+ }
+
+ @Override
+ public InputStream openNonAsset(int cookie, String path, int mode) throws IOException {
+ return open(path);
+ }
+}
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index c7f2c41..7611cde 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -20,7 +20,7 @@
LOCAL_JAR_MANIFEST := manifest.txt
LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-5.0
+ asm-5.2
LOCAL_MODULE := layoutlib_create
diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml
index 368b46b..ac97502 100644
--- a/tools/layoutlib/create/create.iml
+++ b/tools/layoutlib/create/create.iml
@@ -12,9 +12,9 @@
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
- <library name="asm-5.0">
+ <library name="asm-5.2">
<CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 94302d3..a8582c6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -156,6 +156,7 @@
"android.content.res.Resources#getDimensionPixelOffset",
"android.content.res.Resources#getDimensionPixelSize",
"android.content.res.Resources#getDrawable",
+ "android.content.res.Resources#getFont",
"android.content.res.Resources#getIntArray",
"android.content.res.Resources#getInteger",
"android.content.res.Resources#getLayout",
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index 61e381d..488d7d6 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -24,7 +24,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := layoutlib_create junit-host
-LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0
+LOCAL_STATIC_JAVA_LIBRARIES := asm-5.2
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
index 803a7f1..18cab7b 100644
--- a/tools/preload2/src/com/android/preload/DeviceUtils.java
+++ b/tools/preload2/src/com/android/preload/DeviceUtils.java
@@ -16,13 +16,18 @@
package com.android.preload;
+import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.preload.classdataretrieval.hprof.Hprof;
import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.SyncException;
+import com.android.ddmlib.TimeoutException;
+import java.io.File;
+import java.io.IOException;
import java.util.Date;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -32,6 +37,18 @@
*/
public class DeviceUtils {
+ // Locations
+ private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes";
+ // Shell commands
+ private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE;
+ private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art";
+ private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE;
+ private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE;
+ private static final String START_SHELL_CMD = "start";
+ private static final String STOP_SHELL_CMD = "stop";
+ private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system";
+ private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\"";
+
public static void init(int debugPort) {
DdmPreferences.setSelectedDebugPort(debugPort);
@@ -119,43 +136,56 @@
return !ret.contains("No such file or directory");
}
- /**
- * Remove files involved in a standard build that interfere with collecting data. This will
- * remove /etc/preloaded-classes, which determines which classes are allocated already in the
- * boot image. It also deletes any compiled boot image on the device. Then it restarts the
- * device.
- *
- * This is a potentially long-running operation, as the boot after the deletion may take a while.
- * The method will abort after the given timeout.
- */
- public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
- String oldContent =
- DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
- if (oldContent.trim().equals("")) {
- System.out.println("Preloaded-classes already empty.");
- return true;
- }
+ /**
+ * Write over the preloaded-classes file with an empty or existing file and regenerate the boot
+ * image as necessary.
+ *
+ * @param device
+ * @param pcFile
+ * @param bootTimeout
+ * @throws AdbCommandRejectedException
+ * @throws IOException
+ * @throws TimeoutException
+ * @throws SyncException
+ * @return true if successfully overwritten, false otherwise
+ */
+ public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout)
+ throws AdbCommandRejectedException, IOException, TimeoutException, SyncException {
+ boolean writeEmpty = (pcFile == null);
+ if (writeEmpty) {
+ // Check if the preloaded-classes file is already empty.
+ String oldContent =
+ doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS);
+ if (oldContent.trim().equals("")) {
+ System.out.println("Preloaded-classes already empty.");
+ return true;
+ }
+ }
- // Stop the system server etc.
- doShell(device, "stop", 100, TimeUnit.MILLISECONDS);
+ // Stop the system server etc.
+ doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS);
+ // Remount the read-only system partition
+ doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS);
+ // Delete the preloaded-classes file
+ doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS);
+ // Delete the dalvik cache files
+ doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS);
+ if (writeEmpty) {
+ // Write an empty preloaded-classes file
+ doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS);
+ } else {
+ // Push the new preloaded-classes file
+ device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE);
+ }
+ // Manually reset the boot complete flag
+ doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS);
+ // Restart system server on the device
+ doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS);
+ // Wait for the boot complete flag and return the outcome.
+ return waitForBootComplete(device, bootTimeout);
+ }
- // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
- // but AndroidDebugBridge doesn't expose it.
- doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
- doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
- // We do need an empty file.
- doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
-
- // Delete the files in the dalvik cache.
- doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);
-
- // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
- // doesn't reset it, so do it manually.
- doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);
-
- // Start the system server.
- doShell(device, "start", 100, TimeUnit.MILLISECONDS);
-
+ private static boolean waitForBootComplete(IDevice device, long timeout) {
// Do a loop checking each second whether bootcomplete. Wait for at most the given
// threshold.
Date startDate = new Date();
@@ -178,7 +208,7 @@
Date endDate = new Date();
long seconds =
TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
- if (seconds > preloadedWaitTimeInSeconds) {
+ if (seconds > timeout) {
return false;
}
}
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
index c42a19b..2265e95 100644
--- a/tools/preload2/src/com/android/preload/Main.java
+++ b/tools/preload2/src/com/android/preload/Main.java
@@ -29,6 +29,7 @@
import com.android.preload.actions.ScanAllPackagesAction;
import com.android.preload.actions.ScanPackageAction;
import com.android.preload.actions.ShowDataAction;
+import com.android.preload.actions.WritePreloadedClassesAction;
import com.android.preload.classdataretrieval.ClassDataRetriever;
import com.android.preload.classdataretrieval.hprof.Hprof;
import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
@@ -96,6 +97,7 @@
public final static String COMPUTE_FILE_CMD = "comp";
public final static String EXPORT_CMD = "export";
public final static String IMPORT_CMD = "import";
+ public final static String WRITE_CMD = "write";
/**
* @param args
@@ -132,6 +134,7 @@
null));
actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
CLASS_PRELOAD_BLACKLIST));
+ actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel));
actions.add(new ShowDataAction(dataTableModel));
actions.add(new ImportAction(dataTableModel));
actions.add(new ExportAction(dataTableModel));
@@ -200,6 +203,11 @@
ui.input(it.next());
ui.confirmYes();
ui.output(new File(it.next()));
+ // Operation: Write preloaded classes from a specific file
+ } else if (WRITE_CMD.equals(op)) {
+ System.out.println("Writing preloaded classes.");
+ ui.action(WritePreloadedClassesAction.class);
+ ui.input(new File(it.next()));
}
}
} catch (NoSuchElementException e) {
@@ -305,8 +313,16 @@
Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
+ "long time. Please be patient.");
- if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) {
- Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!");
+ boolean success = false;
+ try {
+ success = DeviceUtils.overwritePreloaded(device, null, 15 * 60);
+ } catch (Exception e) {
+ System.err.println(e);
+ } finally {
+ if (!success) {
+ Main.getUI().showMessageDialog(
+ "Removing preloaded-classes failed unexpectedly!");
+ }
}
}
}
diff --git a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
new file mode 100644
index 0000000..9b97f11
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.preload.actions;
+
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.preload.ClientUtils;
+import com.android.preload.DeviceUtils;
+import com.android.preload.DumpData;
+import com.android.preload.DumpTableModel;
+import com.android.preload.Main;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.Date;
+import java.util.Map;
+
+public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction {
+ private File preloadedClassFile;
+
+ public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
+ super("Write preloaded classes action", device);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File[] files = Main.getUI().showOpenDialog(true);
+ if (files != null && files.length > 0) {
+ preloadedClassFile = files[0];
+ super.actionPerformed(e);
+ }
+ }
+
+ @Override
+ public void run() {
+ Main.getUI().showWaitDialog();
+ try {
+ // Write the new file with a 5-minute timeout
+ DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60);
+ } catch (Exception e) {
+ System.err.println(e);
+ } finally {
+ Main.getUI().hideWaitDialog();
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index c53cd3c..7a3cddf 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -49,6 +49,14 @@
private WifiSsid() {
}
+ public static WifiSsid createFromByteArray(byte ssid[]) {
+ WifiSsid wifiSsid = new WifiSsid();
+ if (ssid != null) {
+ wifiSsid.octets.write(ssid, 0/* the start offset */, ssid.length);;
+ }
+ return wifiSsid;
+ }
+
public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
WifiSsid a = new WifiSsid();
a.convertToBytes(asciiEncoded);
diff --git a/wifi/tests/src/android/net/wifi/WifiSsidTest.java b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
new file mode 100644
index 0000000..c7bdb7b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.net.wifi;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiSsid}.
+ */
+public class WifiSsidTest {
+
+ private static final byte[] TEST_SSID =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ /**
+ * Check that createFromByteArray() works.
+ */
+ @Test
+ public void testCreateFromByteArray() {
+ WifiSsid wifiSsid = WifiSsid.createFromByteArray(TEST_SSID);
+ assertTrue(wifiSsid != null);
+ assertEquals(new String(TEST_SSID), wifiSsid.toString());
+ }
+}